Apache Object Pool源码解析

解读Thrift服务获取代码时发现有使用对象池技术, 顺便研究一下对象池技术

基础知识

  • wait/notify

wait/notify是Object对象自带的两个方法, 适用场景是线程在获得某对象的锁之后, 若有一个长时间的IO操作, 可以先尝试放弃对象的锁, 具体可见JVM源码分析之Object.wait/notify实现

关于参考文章中有一张图
Apache Object Pool源码解析_第1张图片
wait_set.jpg

JDK中关于wait函数的解释翻译一下:

  • wait/wait(long)函数会导致线程T一直等待下去直到notify, notifyAll或者等待时间超时

  • 线程T必须获取对象的监视器(monitor)才能调用该对象的wait函数, 参考上图

  • wait函数会让当前线程将自己放置到对象的wait set中, 并且放弃在该对象上获得的同步声明, 该线程在以下四件事发生之前不再接受调度, 陷入休眠

    1. 某线程调用了该对象的notify函数, 这样会随机唤醒一个等待线程
    2. 某线程调用了该对象的notifyAll函数, 会唤醒该对象wait_set中的所有线程
    3. 其他线程调用了线程T的interrupt函数
    4. wait函数的timeout超时, 若timeout为0, 则会永远等待下去
  • 线程T随后会从wait set中移除并且重新接受调度, 它之后会按照正常的方式跟其他线程竞争对象的同步声明, 一旦线程T获得了对象的控制权, 线程T在该对象上的同步声明都会被恢复到调用wait函数之前

  • 值得注意的是, 如果当前线程在wait之前或者wait过程中被其他函数调用Interrupt中断,InterrupttedException将会被抛出, 但是直到T在该对象上的同步声明都恢复之后才会被抛出

  • 线程在调用对象的wait方法的时候, 只会释放该对象的monitor, 该对象获得的其他对象的monitor并不会被释放

  • Commons-Pool

参考文章Apache Commons-Pool 源码分析

  1. ObjectPool


    Apache Object Pool源码解析_第2张图片
    ObjectPool对象池
  • 从图中可以看到ObjectPool对象池的两个关键角色ObjectPool和PoolableObjectFactory. 前者直接对外提供接口, 封装了对象缓存, 创建等细节; 后者需要提供业务无关的对象创建, 销毁, 验证, 激活, 钝化等方法. 通常PoolableObjectFactory需要业务方提供.


    ObjectPool继承结构
  • 常用的是GenericObjectPool, 这个实现类相对复杂一些, 接下来的源码分析主要针对GenericObjectPool

  1. KeyedObjectPool


    Apache Object Pool源码解析_第3张图片
    KeyedObjectPool对象池

    KeyedObjectPool相当于在ObjectPool之上封装了一层, 对不同的key各自提供一个ObjectPool

接下来将主要分析ObjectPool的实现类GenericObjectPool, 然后从代码的层面上阐述两者的关联和区别, 最后说明一下对象池的适用场景和不适用场景

GenericObjectPool源码分析

本文基于common-pools 1.6版本的GenericObjectPool Java Source版本分析

  1. 类注释, 在文件的34 ~ 191行

    • GenericObjectPool提供了一些可配置的参数如下
      • maxActive: 控制对象池可以分配的对象最大数目(同一时间已经被租出的对象或等待被租出的对象), 如果这个数值是负数, 则表示对象池分配的对象没有限制, 当maxActive达到时候, 我们称对象池处于"Exhausted"(枯竭)状态, 默认值为8
      • maxIdle表示对象池中最多可以空闲多少个对象, 如果是负数表示没有限制, 默认为8
      • whenExhaustedAction表示对象池枯竭的时候执行borrowObject执行的行为
        • WHEN_EXHAUSTED_FAIL: 抛出NoSuchElementException
        • WHEN_EXHAUSTED_GROW: 创建新对象并返回(maxActive此时不生效)
        • WHEN_EXHAUSTED_BLOCK: borrowObject对象将会被阻塞, 直到一个新的或者空闲的对象可用, 如果此时maxWait是正数, 则borrowObject对象将会被阻塞maxWait毫秒, 然后将会抛出一个NoSuchElementException, 如果maxWait是负数, 则borrowObject函数可以被无限制block
          默认情况下whenExhaustedAction=WHEN_EXHAUSTED_BLOCK, 且maxWait被赋值为-1
      • testOnBorrow: 如果设置为true, 则在borrowObject返回对象之前都需要调用PoolableObjectFactory的validateObject函数, 验证失败的对象将会被对象池丢弃, 另外一个不同的对象将会被返回, 默认false
      • testOnReturn: 类似上文, 对象返回给对象池之前需要验证.
      • 可以配置"idle object eviction"线程用来实现验证或者驱赶线程池中的空闲对象, 以便使得维持对象池的最小的空闲对象, 该线程可以配置如下参数
        • timeBetweenEvictionRunsMillis: 线程间歇时间, 负数表示线程不执行
        • minEvictableIdleTimeMillis: 对象无效的时候最多可以在线程池中保留多长时间
        • testWhileIdle: 标明是否需要对空闲对象调用factory的validateObject函数, 验证失败的对象将会被清理掉
        • .......
    • 对象池可以配置成LIFO队列--- 总是返回最近使用的对象; 或者是FIFO对象, 返回空闲对象中最老的对象, setLifo可以设置对应的行为
    • 必须为GenericObjectPool提供factory否则它是不可用的, 可以使用构造函数或者set函数注入
    • 为了避免可能的死锁, 需要确保在同步代码中不要调用factory method
  2. 构造函数

    • PoolableObjectFactory factory,
    • int maxActive,
    • byte whenExhaustedAction,
    • long maxWait,
    • int maxIdle,
    • int minIdle,
    • boolean testOnBorrow,
    • boolean testOnReturn,
    • long timeBetweenEvictionRunsMillis,
    • int numTestsPerEvictionRun,
    • long minEvictableIdleTimeMillis,
    • boolean testWhileIdle
  3. allocate函数
    allocate函数用于为线程分配对象, 先看下注释说明: 首先为allocation队列中的latch分配所有可用的对象, 然后在maxActive的限制范围内尽可能的为latch分配mayCreated状态.值得注意的是这个方法在GenericObjectPool中是安全的, 但是在GenericKeyedObjectPool中为了实现一致性不要在同步块中调用该函数.

    private synchronized void allocate() {
        if (isClosed()) return;

        // First use any objects in the pool to clear the queue
        for (;;) {
            if (!_pool.isEmpty() && !_allocationQueue.isEmpty()) {
                Latch latch = _allocationQueue.removeFirst();
                latch.setPair( _pool.removeFirst());
                _numInternalProcessing++;
                synchronized (latch) {
                    latch.notify(); // 注意这里的notify,下文有解释
                }
            } else {
                break;
            }
        }

        // Second utilise any spare capacity to create new objects
        for(;;) {
            if((!_allocationQueue.isEmpty()) && (_maxActive < 0 || (_numActive + _numInternalProcessing) < _maxActive)) {
                Latch latch = _allocationQueue.removeFirst();
                latch.setMayCreate(true);
                _numInternalProcessing++;
                synchronized (latch) {
                    latch.notify(); // 注意这里的notify,下文有解释
                }
            } else {
                break;
            }
        }
    }

关于allocate函数有几点解释

  • allocate函数调用的时机
    • 配置参数发生变化: maxActive, whenExhaustedAction, maxWait, maxIdle, minIdle,
    • 运行时数据发生变化: _allocationQueue, _numInternalProcessing, numActive等
    • 函数调用, 导致前两者变化
  • allocate函数的思想: 先分配idle对象, 再在maxActive的限制内允许创建对象, maxActive > numActive + numInternalProcessing
    • numActive表示的从pool中borrow且尚未归还的对象数目;
    • internalProcessing表示处于处理过程中的对象数目(通常是创建中或者销毁中), 他们既不是激活状态也不是空闲状态
  • 上文源码中在为latch分配实际对象或者mayCreate状态之后, 都会调用latch.notify(). 一个场景是: A线程希望borrow一个对象, 但当前pool中没有空闲对象, 当前的maxActive也不允许继续创建对象,即对象池处于"枯竭"的状态. 若对象池配置的枯竭策略是WHEN_EXHAUSTED_BLOCK, 调用线程可能会在latch上wait(参考下文中的borrowObject源码); 当B线程更改配置参数或者调用函数更改了运行时数据, 会触发对象池的重新allocate, 这时候A线程的latch可能会被分配, 这时候它需要唤醒在latchA上wait的A线程, 继续执行操作.
  1. borrowObject函数
1.    public T borrowObject() throws Exception {
2.        long starttime = System.currentTimeMillis();
3.        Latch latch = new Latch();
4.        byte whenExhaustedAction;
5.        long maxWait;
6.        synchronized (this) {
7.            // Get local copy of current config. Can't sync when used later as
8.            // it can result in a deadlock. Has the added advantage that config
9.            // is consistent for entire method execution
10.            whenExhaustedAction = _whenExhaustedAction;
11.            maxWait = _maxWait;
12.
13.            // Add this request to the queue
14.            _allocationQueue.add(latch);
15.        }
16.        // Work the allocation queue, allocating idle instances and
17.        // instance creation permits in request arrival order
18.        allocate();
19.
20.        for(;;) {
21.            synchronized (this) {
22.                assertOpen();
23.            }
24.
25.            // If no object was allocated from the pool above
26.            if(latch.getPair() == null) {
27.                // check if we were allowed to create one
28.                if(latch.mayCreate()) {
29.                    // allow new object to be created
30.                } else {
31.                    // the pool is exhausted
32.                    switch(whenExhaustedAction) {
33.                        case WHEN_EXHAUSTED_GROW:
34.                            // allow new object to be created
35.                            synchronized (this) {
36.                                // Make sure another thread didn't allocate us an object
37.                                // or permit a new object to be created
38.                                if (latch.getPair() == null && !latch.mayCreate()) {
39.                                    _allocationQueue.remove(latch);
40.                                    _numInternalProcessing++;
41.                                }
42.                            }
43.                            break;
44.                        case WHEN_EXHAUSTED_FAIL:
45.                            synchronized (this) {
46.                                // Make sure allocate hasn't already assigned an object
47.                                // in a different thread or permitted a new object to be created
48.                                if (latch.getPair() != null || latch.mayCreate()) {
49.                                    break;
50.                                }
51.                                _allocationQueue.remove(latch);
52.                            }
53.                            throw new NoSuchElementException("Pool exhausted");
54.                        case WHEN_EXHAUSTED_BLOCK:
55.                            try {
56.                                synchronized (latch) {
57.                                    // Before we wait, make sure another thread didn't allocate us an object
58.                                    // or permit a new object to be created
59.                                    if (latch.getPair() == null && !latch.mayCreate()) {
60.                                        if(maxWait <= 0) {
61.                                            latch.wait();
62.                                        } else {
63.                                            // this code may be executed again after a notify then continue cycle
64.                                            // so, need to calculate the amount of time to wait
65.                                            final long elapsed = (System.currentTimeMillis() - starttime);
66.                                            final long waitTime = maxWait - elapsed;
67.                                            if (waitTime > 0)
68.                                            {
69.                                                latch.wait(waitTime);
70.                                            }
71.                                        }
72.                                    } else {
73.                                        break;
74.                                    }
75.                                }
76.                                // see if we were awakened by a closing pool
77.                                if(isClosed() == true) {
78.                                    throw new IllegalStateException("Pool closed");
79.                                }
80.                            } catch(InterruptedException e) {
81.                                boolean doAllocate = false;
82.                                synchronized(this) {
83.                                    // Need to handle the all three possibilities
84.                                    if (latch.getPair() == null && !latch.mayCreate()) {
85.                                        // Case 1: latch still in allocation queue
86.                                        // Remove latch from the allocation queue
87.                                        _allocationQueue.remove(latch);
88.                                    } else if (latch.getPair() == null && latch.mayCreate()) {
89.                                        // Case 2: latch has been given permission to create
90.                                        //         a new object
91.                                        _numInternalProcessing--;
92.                                        doAllocate = true;
93.                                    } else {
94.                                        // Case 3: An object has been allocated
95.                                        _numInternalProcessing--;
96.                                        _numActive++;
97.                                        returnObject(latch.getPair().getValue());
98.                                    }
99.                                }
100.                                if (doAllocate) {
101.                                    allocate();
102.                                }
103.                                Thread.currentThread().interrupt();
104.                                throw e;
105.                            }
106.                            if(maxWait > 0 && ((System.currentTimeMillis() - starttime) >= maxWait)) {
107.                                synchronized(this) {
108.                                    // Make sure allocate hasn't already assigned an object
109.                                    // in a different thread or permitted a new object to be created
110.                                    if (latch.getPair() == null && !latch.mayCreate()) {
111.                                        // Remove latch from the allocation queue
112.                                        _allocationQueue.remove(latch);
113.                                    } else {
114.                                        break;
115.                                    }
116.                                }
117.                                throw new NoSuchElementException("Timeout waiting for idle object");
118.                            } else {
119.                                continue; // keep looping
120.                            }
121.                        default:
122.                            throw new IllegalArgumentException("WhenExhaustedAction property " + whenExhaustedAction +
123.                                    " not recognized.");
124.                    }
125.                }
126.            }
127.
128.            boolean newlyCreated = false;
129.            if(null == latch.getPair()) {
130.                try {
131.                    T obj = _factory.makeObject();
132.                    latch.setPair(new ObjectTimestampPair(obj));
133.                    newlyCreated = true;
134.                } finally {
135.                    if (!newlyCreated) {
136.                        // object cannot be created
137.                        synchronized (this) {
138.                            _numInternalProcessing--;
139.                            // No need to reset latch - about to throw exception
140.                        }
141.                        allocate();
142.                    }
143.                }
144.            }
145.            // activate & validate the object
146.            try {
147.                _factory.activateObject(latch.getPair().value);
148.                if(_testOnBorrow &&
149.                        !_factory.validateObject(latch.getPair().value)) {
150.                    throw new Exception("ValidateObject failed");
151.                }
152.                synchronized(this) {
153.                    _numInternalProcessing--;
154.                    _numActive++;
155.                }
156.                return latch.getPair().value;
157.            }
158.            catch (Throwable e) {
159.                PoolUtils.checkRethrow(e);
160.                // object cannot be activated or is invalid
161.                try {
162.                    _factory.destroyObject(latch.getPair().value);
163.                } catch (Throwable e2) {
164.                    PoolUtils.checkRethrow(e2);
165.                    // cannot destroy broken object
166.                }
167.                synchronized (this) {
168.                    _numInternalProcessing--;
169.                    if (!newlyCreated) {
170.                        latch.reset();
171.                        _allocationQueue.add(0, latch);
172.                    }
173.                }
174.                allocate();
175.                if(newlyCreated) {
176.                    throw new NoSuchElementException("Could not create a validated object, cause: " + e.getMessage());
177.                }
178.                else {
179.                    continue; // keep looping
180.                }
181.            }
182.        }
183.    }

borrowObject函数非常长, 近200行, 下面逐行分析

  • 6 ~ 15行: 缓存当前的whenExhaustedAction, maxWait参数, 这个是由于配置参数可能会发生变化, 而且为了提升性能, borrowObject函数是逐段占用锁, 为避免死锁等问题, 需要缓存该参数
  • 18行: 会调用allocate进行初次分配, 关于allocate函数已经在前文中阐述清除
  • 26 ~ 126行: 处理那些没有直接从pool中分配对象的请求
    • 若latch有mayCreate状态, 直接跳出
    • 否则, 对象池处于枯竭状态, 检查whenExhaustedAction
      • WHEN_EXHAUSTED_GROW: 忽略maxActive继续分配(注意需要首先检查是不是latch已经被分配了参见allocate函数的第3点说明), 若没有分配, 把请求从allocateQueue中移除
      • WHEN_EXHAUSTED_FAIL: 同上, 首先检查latch是不是已经被分配了, 若没有, 则移除请求抛出异常
      • WHEN_EXHAUSTED_BLOCK: 首先检查latch有没有被分配, 若没有被分配, 则按照下面的说明执行
        • 按照maxWait的配置, 等待(无线等待或者有限等待)
          • 在调用wait期间可能会被其他函数中断, 中断善后处理如下, 处理完成之后, 会调用当前线程的interrupt函数, 抛出InterruptedException, 参考Java 理论与实践: 处理 InterruptedException
            • latch仍然处于待分配状态, 则从队列中移除(同步状态下)
            • 如果已经分配了mayCreate, 则放弃此次创建, _numInternalProcessing--, 且调用allocate()函数把这个机会让给别人
            • 如果已经分配了一个对象, _numInternalProcessing--;_numActive++; 并把这个对象归还到线程池中
        • 若正常等待完成之后(可能是超时), 检查状态, 可能还会抛出NoSuchElementException
  • 128 ~ 144行: 如果latch没有被分配对象则尝试创建对象, 调用factory的makeObject方法创建对象, 若不能创建对象, 需要更改numInternalProcessing, 重新调用allocate把机会让出来给别人
  • 145 ~ 183行: 激活校验对象(无论是新创建的还是从pool中获取的对象)
    • 调用factory的activateObject
    • 若设置了testOnBorrow, 调用factory的validateObject函数, 若失败抛出异常
    • 标记internalProcessing减1, numActive加1
    • 异常处理
      • 确认对象是无法被激活的或者是无效的, 执行factory.destroy函数, 若执行destroy异常, 吞掉异常
      • 执行完成之后更改numInternalProcessing减1, 如果对象不是刚创建的, 则需要把当前latch重新排队, 并调用allocate重新分配
      • 如果是新创建的喜爱对象, 抛出异常, 说明无法创建新对象

其他函数相对比较简单, 理解了borrowObject函数都可以理解

你可能感兴趣的:(Apache Object Pool源码解析)