commons-pool2

  转载请注明源出处:http://www.cnblogs.com/lighten/p/7375611.html

1.前言

  本章介绍一下常用基础Jar包commons-pools2,最近使用到了thrift作为rpc服务通讯,但是没有找到其提供的连接池。百度了一下官方貌似没有提供,需要自己实现,所以根据网上的实现方法通过使用commons-pool2包来构建自己的thrift连接池服务。完成后,顺便研究了一下commons-pool2的实现(怕使用不当有坑),也就有了这篇文章。

2.commons-pool2

  既然名字中有个2也就意味着是第二个版本了,重新命名也就意味着和原版本并不兼容。这是apache提供的一个开源包,在很多地方都会使用,2015年7月之后就不再进行更新了,目前最新版本是2.4.2(最近突然又更新了目前在2.5.0版本)。其准确来说应该是一个对象池,不过是常用在于连接上而已。

  commons-pool2的类并不多,全部42个类如下:

    

  包的结构简单,类不多,而且还有大量的接口和内部类,所以实际上需要关注的类没几个,commons-pool2使用起来也就方便了。

2.1 常见配置

  上面也说了,该类是一个基础包,被很多其它jar包使用,常用于对象池的管理,所以其一些配置,在写代码的时候也会经常接触到类似的,可能是封装过的,也可能是版本1的,不过大同小异,主要是思路。了解了配置的具体作用,再结合代码就能够了解这类jar包是如何写的了。

  通用的配置都在GenericObjectPoolConfig类中:

    maxTotal:对象池中最多允许的对象数,默认8(可能超过,不过超过后使用完了就会销毁,后面源码会介绍相关机制)

    maxIdle:对象池中最多允许存在的空闲对象,默认8

    minIdle:池中最少要保留的对象数,默认0

    lifo:是否使用FIFO先进先出的模式获取对象(空闲对象都在一个队列中),默认为true使用先进先出,false是先进后出

    fairness:是否使用公平锁,默认false(公平锁是线程安全中的概念,true的含义是谁先等待获取锁,随先在锁释放的时候获取锁,如非必要,一般不使用公平锁,会影响性能)

    maxWaitMillis:从池中获取一个对象最长的等待时间,默认-1,含义是无限等,超过这个时间还未获取空闲对象,就会抛出异常。

    minEvictableIdleTimeMillis:最小的驱逐时间,单位毫秒,默认30分钟。这个用于驱逐线程,对象空闲时间超过这个时间,意味着此时系统不忙碌,会减少对象数量。

    evictorShutdownTimeoutMillis:驱逐线程关闭的超时时间,默认10秒。

    softMinEvictableIdleTimeMillis:也是最小的驱逐时间,但是会和另一个指标minIdle一同使用,满足空闲时间超过这个设置,且当前空闲数量比设置的minIdle要大,会销毁该对象。所以,通常该值设置的比minEvictableIdleTimeMillis要小。

    numTestsPerEvictionRun:驱逐线程运行每次测试的对象数量,默认3个。驱逐线程就是用来检查对象空闲状态,通过设置的对象数量等参数,保持对象的活跃度和数量,其是一个定时任务,每次不是检查所有的对象,而是抽查几个,这个就是用于抽查。

    evictionPolicyClassName:驱逐线程使用的策略类名,之前的minEvictableIdleTimeMillis和softMinEvictableIdleTimeMillis就是默认策略DefaultEvictionPolicy的实现,可以自己实现策略。

    testOnCreate:在创建对象的时候是否检测对象,默认false。后续会结合代码说明是如何检测的。

    testOnBorrow:在获取空闲对象的时候是否检测对象是否有效,默认false。这个通常会设置成true,一般希望获取一个可用有效的对象吧。

    testOnReturn:在使用完对象放回池中时是否检测对象是否仍有效,默认false。

    testWhileIdle:在空闲的时候是否检测对象是否有效,这个发生在驱逐线程执行时。

    timeBetweenEvictionRunsMillis:驱逐线程的执行周期,上面说过该线程是个定时任务。默认-1,即不开启驱逐线程,所以与之相关的参数是没有作用的。

    blockWhenExhausted:在对象池耗尽时是否阻塞,默认true。false的话超时就没有作用了。

    jmxEnabled:是否允许jmx的方式创建一个配置实例,默认true。

    jmxNamePrefix:jmx默认的前缀名,默认为pool

    jmxNameBase:jmx默认的base name,默认为null,意味着池提供一个名称。

2.2 基本实现

  上面的配置已经说明了一些内容了,此节介绍对象池的一个基础实现思路。

  首先作为一个对象池,我们需要从池中借对象,借完了要还,还要能创建对象存入池中,校验对象是否还能使用。这个就是一个对象池的基本定义了:

  

  commons-pool2还提供了一种控制细粒度更高的对象池KeyedObjectPool<K,V>。其根据关键字来维护不同的池,在某些场景十分有用,这里不对其做详细介绍,弄明白了一般的线程池,对这种池扩展也就有了思路。从接口到抽象类到具体实现类池经历了下面几个类:ObjectPool->BaseObjectPool->SoftReferenceObjectPool,这种用的比较少,看名次也知道是软引用的池,另一种是BaseGenericObjectPool->GenericObjectPool。通常我们会继承GenericObjectPool来设计自己的对象池。

  有了池之后,我们需要一个创建池对象的类,这个就是工厂类:PooledObjectFactory。其要提供一个对象的生命周期的各个操作,包括创建、销毁、校验有效性、激活和钝化对象。

  同样,其提供了一个抽象类BasePooledObjectFactory,这个就没有具体的实现类了,因为涉及到不同对象,不同对象的管理方法不同,不好抽象。我们需要做的就是继承BasePooledObjectFactory对象,实现其未实现或者空实现的方法了,即上述截图的方法。

  有了池来管理对象使用,有了工厂来管理对象的生命周期,一般而言也就够了。但是还有一个重要的环节就是将池与工厂连接起来的对象的定义,所以要进行抽象。这就是PooledObject的作用了,其定义的一系列方法,将我们的对象和池以及工厂,通过这个接口关联了起来。实现类是DefaultPooledObject,通常使用这个就够了,不需要扩展。如果业务上对对象有更细致的控制,可以继承或者直接自己实现PooledObject。

  到此也就剩下一个驱逐线程没介绍了,其维持着池的健康,或者说是活力。以上就是一个对象池的基本内容:池本身,创建对象的工厂,清洁池的驱逐线程,关联池和工厂和自己创建的对象的池对象规范。这样也就没几个类没介绍过了,剩下的就是UsageTracking,看名称也能知道是干啥的了,后面介绍详细流程的时候会顺带一提,用的不多。

2.3 具体实现

  下面结合具体代码构建一个对象池,来说明该池的是怎么工作的。首先该对象的基本定义如下:需要一个对象类型Student,需要一个连接池管理对象CommonObjectPool,需要一个制造对象的工厂StudentFactory。其大体代码如下:

  

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}



public class StudentFactory extends BasePooledObjectFactory<Student> {

    private Random random = new Random();

    public Student create() throws Exception {

        int age = random.nextInt(100);
        Student student = new Student("commons", age);
        System.out.println("创建对象:" + student);
        return student;
    }

    public PooledObject<Student> wrap(Student obj) {
        return new DefaultPooledObject<Student>(obj);
    }

    @Override
    public void destroyObject(PooledObject<Student> p) throws Exception {
        System.out.println("销毁对象:" + p.getObject());
        super.destroyObject(p);
    }

    @Override
    public boolean validateObject(PooledObject<Student> p) {
        System.out.println("校验对象是否可用:" + p.getObject());
        return super.validateObject(p);
    }

    @Override
    public void activateObject(PooledObject<Student> p) throws Exception {
        System.out.println("激活钝化的对象系列操作:" + p.getObject());
        super.activateObject(p);
    }

    @Override
    public void passivateObject(PooledObject<Student> p) throws Exception {
        System.out.println("钝化未使用的对象:" + p.getObject());
        super.passivateObject(p);
    }
}


public class CommonObjectPool extends GenericObjectPool<Student> {

    public CommonObjectPool(PooledObjectFactory<Student> factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) {
        super(factory, config, abandonedConfig);
    }
}

  使用方法如下:

public class Test {

    public static void main(String[] args) {
        StudentFactory studentFactory = new StudentFactory();
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        AbandonedConfig abandonedConfig = new AbandonedConfig();
        CommonObjectPool pool = new CommonObjectPool(studentFactory, config, abandonedConfig);

        Student student = null;
        try {
            student = pool.borrowObject();
            System.out.println(student);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(student != null) pool.returnObject(student);
        }
    }
}

  下面结合源码和例子讲解执行过程,先是通过对象工厂类和配置初始化了一个pool,pool的初始化操作代码如下:

public GenericObjectPool(final PooledObjectFactory<T> factory,
            final GenericObjectPoolConfig config) {

        super(config, ONAME_BASE, config.getJmxNamePrefix());

        if (factory == null) {
            jmxUnregister(); // tidy up
            throw new IllegalArgumentException("factory may not be null");
        }
        this.factory = factory;

        idleObjects = new LinkedBlockingDeque<>(config.getFairness());

        setConfig(config);

        startEvictor(getTimeBetweenEvictionRunsMillis());
 }

  主要的工作就是设置工厂类,配置,开启驱逐线程。下面先介绍驱逐线程的工作机制:

final void startEvictor(final long delay) {
        synchronized (evictionLock) {
            if (null != evictor) {
                EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS);
                evictor = null;
                evictionIterator = null;
            }
            if (delay > 0) {
                evictor = new Evictor();
                EvictionTimer.schedule(evictor, delay, delay);
            }
        }
}

  如果设置过了就会关闭,不然要delay大于0才会开启该线程,该值就是config中的getTimeBetweenEvictionRunsMillis。开启方式就是通过EvictionTimer的周期任务,这实际上就是一个Timer定时器。该定时器做的工作如下:

  

    public void run() {
            final ClassLoader savedClassLoader =
                    Thread.currentThread().getContextClassLoader();
            try {
                if (factoryClassLoader != null) {
                    // Set the class loader for the factory
                    final ClassLoader cl = factoryClassLoader.get();
                    if (cl == null) {
                        // The pool has been dereferenced and the class loader
                        // GC'd. Cancel this timer so the pool can be GC'd as
                        // well.
                        cancel();
                        return;
                    }
                    Thread.currentThread().setContextClassLoader(cl);
                }

                // Evict from the pool
                try {
                    evict();
                } catch(final Exception e) {
                    swallowException(e);
                } catch(final OutOfMemoryError oome) {
                    // Log problem but give evictor thread a chance to continue
                    // in case error is recoverable
                    oome.printStackTrace(System.err);
                }
                // Re-create idle instances.
                try {
                    ensureMinIdle();
                } catch (final Exception e) {
                    swallowException(e);
                }
            } finally {
                // Restore the previous CCL
                Thread.currentThread().setContextClassLoader(savedClassLoader);
            }
        }

  可以看出,先进行了驱逐,再判断是否小于minIdle的设置,小于就会再次创建对象。

  

private void ensureIdle(final int idleCount, final boolean always) throws Exception {
        if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
            return;
        }

        while (idleObjects.size() < idleCount) {
            final PooledObject<T> p = create();
            if (p == null) {
                // Can't create objects, no reason to think another call to
                // create will work. Give up.
                break;
            }
            if (getLifo()) {
                idleObjects.addFirst(p);
            } else {
                idleObjects.addLast(p);
            }
        }
        if (isClosed()) {
            // Pool closed while object was being added to idle objects.
            // Make sure the returned object is destroyed rather than left
            // in the idle object pool (which would effectively be a leak)
            clear();
        }
}

  就是调用create方法创建,根据lifo的参数决定是先入先出还是后入先出。evict方法主要做了如下操作:

    public void evict() throws Exception {
        assertOpen();

        if (idleObjects.size() > 0) {

            PooledObject<T> underTest = null;
            final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();

            synchronized (evictionLock) {
                final EvictionConfig evictionConfig = new EvictionConfig(
                        getMinEvictableIdleTimeMillis(),
                        getSoftMinEvictableIdleTimeMillis(),
                        getMinIdle());

                final boolean testWhileIdle = getTestWhileIdle();

                for (int i = 0, m = getNumTests(); i < m; i++) {
                    if (evictionIterator == null || !evictionIterator.hasNext()) {
                        evictionIterator = new EvictionIterator(idleObjects);
                    }
                    if (!evictionIterator.hasNext()) {
                        // Pool exhausted, nothing to do here
                        return;
                    }

                    try {
                        underTest = evictionIterator.next();
                    } catch (final NoSuchElementException nsee) {
                        // Object was borrowed in another thread
                        // Don't count this as an eviction test so reduce i;
                        i--;
                        evictionIterator = null;
                        continue;
                    }

                    if (!underTest.startEvictionTest()) {
                        // Object was borrowed in another thread
                        // Don't count this as an eviction test so reduce i;
                        i--;
                        continue;
                    }

                    // User provided eviction policy could throw all sorts of
                    // crazy exceptions. Protect against such an exception
                    // killing the eviction thread.
                    boolean evict;
                    try {
                        evict = evictionPolicy.evict(evictionConfig, underTest,
                                idleObjects.size());
                    } catch (final Throwable t) {
                        // Slightly convoluted as SwallowedExceptionListener
                        // uses Exception rather than Throwable
                        PoolUtils.checkRethrow(t);
                        swallowException(new Exception(t));
                        // Don't evict on error conditions
                        evict = false;
                    }

                    if (evict) {
                        destroy(underTest);
                        destroyedByEvictorCount.incrementAndGet();
                    } else {
                        if (testWhileIdle) {
                            boolean active = false;
                            try {
                                factory.activateObject(underTest);
                                active = true;
                            } catch (final Exception e) {
                                destroy(underTest);
                                destroyedByEvictorCount.incrementAndGet();
                            }
                            if (active) {
                                if (!factory.validateObject(underTest)) {
                                    destroy(underTest);
                                    destroyedByEvictorCount.incrementAndGet();
                                } else {
                                    try {
                                        factory.passivateObject(underTest);
                                    } catch (final Exception e) {
                                        destroy(underTest);
                                        destroyedByEvictorCount.incrementAndGet();
                                    }
                                }
                            }
                        }
                        if (!underTest.endEvictionTest(idleObjects)) {
                            // TODO - May need to add code here once additional
                            // states are used
                        }
                    }
                }
            }
        }
        final AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
            removeAbandoned(ac);
        }
    }

  先是判断池是开启状态,且空闲对象要大于0,不然不需要驱逐。然后循环了设置的numTests的次数,一次驱逐就检查这么多个对象。后面一段是并发被干扰的一些操作,主要是保证被干扰后仍检查这么些对象。最后就是根据驱逐策略来驱逐对象。上面配置项说过是怎么回事,具体见DefaultEvictionPolicy。如果判断是驱逐,就调用destory方法销毁对象。否则,判断testWhileIdle配置项,决定是否校验对象是否仍可用,先激活对象activateObject,有异常直接销毁。否则开始校验对象的可用性,validateObject。失败销毁,成功就钝化变成原样子。钝化失败也直接销毁。最后是一个遗弃对象的设置,就是说有些对象借出去了由于种种原因,比如写法上的问题,导致对象很久没有还回来,这个设置就是用于清理这类对象的。这类对象不再被池借出,但又暂用了资源。一般而言该配置很少用到,因为写方式通常都将return操作放在finally模块,不会出现此类情况。

  最后我们看下借对象和还对象都做了哪些操作吧。

    public T borrowObject(final long borrowMaxWaitMillis) throws Exception {
        assertOpen();

        final AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
                (getNumIdle() < 2) &&
                (getNumActive() > getMaxTotal() - 3) ) {
            removeAbandoned(ac);
        }

        PooledObject<T> p = null;

        // Get local copy of current config so it is consistent for entire
        // method execution
        final boolean blockWhenExhausted = getBlockWhenExhausted();

        boolean create;
        final long waitTime = System.currentTimeMillis();

        while (p == null) {
            create = false;
            p = idleObjects.pollFirst();
            if (p == null) {
                p = create();
                if (p != null) {
                    create = true;
                }
            }
            if (blockWhenExhausted) {
                if (p == null) {
                    if (borrowMaxWaitMillis < 0) {
                        p = idleObjects.takeFirst();
                    } else {
                        p = idleObjects.pollFirst(borrowMaxWaitMillis,
                                TimeUnit.MILLISECONDS);
                    }
                }
                if (p == null) {
                    throw new NoSuchElementException(
                            "Timeout waiting for idle object");
                }
            } else {
                if (p == null) {
                    throw new NoSuchElementException("Pool exhausted");
                }
            }
            if (!p.allocate()) {
                p = null;
            }

            if (p != null) {
                try {
                    factory.activateObject(p);
                } catch (final Exception e) {
                    try {
                        destroy(p);
                    } catch (final Exception e1) {
                        // Ignore - activation failure is more important
                    }
                    p = null;
                    if (create) {
                        final NoSuchElementException nsee = new NoSuchElementException(
                                "Unable to activate object");
                        nsee.initCause(e);
                        throw nsee;
                    }
                }
                if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
                    boolean validate = false;
                    Throwable validationThrowable = null;
                    try {
                        validate = factory.validateObject(p);
                    } catch (final Throwable t) {
                        PoolUtils.checkRethrow(t);
                        validationThrowable = t;
                    }
                    if (!validate) {
                        try {
                            destroy(p);
                            destroyedByBorrowValidationCount.incrementAndGet();
                        } catch (final Exception e) {
                            // Ignore - validation failure is more important
                        }
                        p = null;
                        if (create) {
                            final NoSuchElementException nsee = new NoSuchElementException(
                                    "Unable to validate object");
                            nsee.initCause(validationThrowable);
                            throw nsee;
                        }
                    }
                }
            }
        }

        updateStatsBorrow(p, System.currentTimeMillis() - waitTime);

        return p.getObject();
    }

  借的操作步骤如下:先确定池是否开启,再根据条件决定是否移除遗弃的对象。开始获取对象:1.从idle中获取一个,没获取到就创建一个,创建的逻辑涉及参数maxTotal,超过这个值不会创建对象,返回null,maxTotal为-1意为创建的数量为无限(整数最大)。2.创建失败,线程阻塞,等待时间为-1就一直等待,不为-1等到指定时间还没等到,就抛出异常。3.不等待直接会在没获取对象的时候直接抛出异常。4.对象状态不对,没有锁定,置为null。5.上述都没问题,获取对象后开始激活对象,失败销毁对象。成功后判断是否borrow和create的时候要校验对象可用性,需要进行校验,校验失败销毁。上诉是一个while(p==null)的循环,所以borrow的结果只有2种,1是借不到对象超时,2是借到对象。其他就是等待获取空闲对象。

  还对象的逻辑也不难:

    public void returnObject(final T obj) {
        final PooledObject<T> p = allObjects.get(new IdentityWrapper<>(obj));

        if (p == null) {
            if (!isAbandonedConfig()) {
                throw new IllegalStateException(
                        "Returned object not currently part of this pool");
            }
            return; // Object was abandoned and removed
        }

        synchronized(p) {
            final PooledObjectState state = p.getState();
            if (state != PooledObjectState.ALLOCATED) {
                throw new IllegalStateException(
                        "Object has already been returned to this pool or is invalid");
            }
            p.markReturning(); // Keep from being marked abandoned
        }

        final long activeTime = p.getActiveTimeMillis();

        if (getTestOnReturn()) {
            if (!factory.validateObject(p)) {
                try {
                    destroy(p);
                } catch (final Exception e) {
                    swallowException(e);
                }
                try {
                    ensureIdle(1, false);
                } catch (final Exception e) {
                    swallowException(e);
                }
                updateStatsReturn(activeTime);
                return;
            }
        }

        try {
            factory.passivateObject(p);
        } catch (final Exception e1) {
            swallowException(e1);
            try {
                destroy(p);
            } catch (final Exception e) {
                swallowException(e);
            }
            try {
                ensureIdle(1, false);
            } catch (final Exception e) {
                swallowException(e);
            }
            updateStatsReturn(activeTime);
            return;
        }

        if (!p.deallocate()) {
            throw new IllegalStateException(
                    "Object has already been returned to this pool or is invalid");
        }

        final int maxIdleSave = getMaxIdle();
        if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
            try {
                destroy(p);
            } catch (final Exception e) {
                swallowException(e);
            }
        } else {
            if (getLifo()) {
                idleObjects.addFirst(p);
            } else {
                idleObjects.addLast(p);
            }
            if (isClosed()) {
                // Pool closed while object was being added to idle objects.
                // Make sure the returned object is destroyed rather than left
                // in the idle object pool (which would effectively be a leak)
                clear();
            }
        }
        updateStatsReturn(activeTime);
    }

  更新对象状态,判断还的时候是否要校验对象可用性,不可用销毁。之后钝化对象,钝化失败销毁,超过maxIdle也直接销毁。最后根据lifo来确定放回方式。因为涉及销毁对象,所以都要进行确定minidle来决定是否补充对象。

3.结束语

  commons-pool2的主要逻辑就是上述内容了,代码例子也给了一个。这里总结一下对象的一个生命周期:create->activate->invalidate->borrow->invalidate->return->destory。其中validate阶段发生在各个环节,主要通过TestOnXXX进行配置决定。

 

posted @ 2018-04-15 21:29  dark_saber  阅读(5678)  评论(0编辑  收藏  举报