解读Thrift服务获取代码时发现有使用对象池技术, 顺便研究一下对象池技术
基础知识
-
wait/notify
wait/notify是Object对象自带的两个方法, 适用场景是线程在获得某对象的锁之后, 若有一个长时间的IO操作, 可以先尝试放弃对象的锁, 具体可见JVM源码分析之Object.wait/notify实现
关于参考文章中有一张图JDK中关于wait函数的解释翻译一下:
wait/wait(long)函数会导致线程T一直等待下去直到notify, notifyAll或者等待时间超时
线程T必须获取对象的监视器(monitor)才能调用该对象的wait函数, 参考上图
-
wait函数会让当前线程将自己放置到对象的wait set中, 并且放弃在该对象上获得的同步声明, 该线程在以下四件事发生之前不再接受调度, 陷入休眠
- 某线程调用了该对象的notify函数, 这样会随机唤醒一个等待线程
- 某线程调用了该对象的notifyAll函数, 会唤醒该对象wait_set中的所有线程
- 其他线程调用了线程T的interrupt函数
- wait函数的timeout超时, 若timeout为0, 则会永远等待下去
线程T随后会从wait set中移除并且重新接受调度, 它之后会按照正常的方式跟其他线程竞争对象的同步声明, 一旦线程T获得了对象的控制权, 线程T在该对象上的同步声明都会被恢复到调用wait函数之前
值得注意的是, 如果当前线程在wait之前或者wait过程中被其他函数调用Interrupt中断,InterrupttedException将会被抛出, 但是直到T在该对象上的同步声明都恢复之后才会被抛出
线程在调用对象的wait方法的时候, 只会释放该对象的monitor, 该对象获得的其他对象的monitor并不会被释放
-
Commons-Pool
参考文章Apache Commons-Pool 源码分析
-
ObjectPool
-
从图中可以看到ObjectPool对象池的两个关键角色ObjectPool和PoolableObjectFactory. 前者直接对外提供接口, 封装了对象缓存, 创建等细节; 后者需要提供业务无关的对象创建, 销毁, 验证, 激活, 钝化等方法. 通常PoolableObjectFactory需要业务方提供.
常用的是GenericObjectPool, 这个实现类相对复杂一些, 接下来的源码分析主要针对GenericObjectPool
-
KeyedObjectPool
KeyedObjectPool相当于在ObjectPool之上封装了一层, 对不同的key各自提供一个ObjectPool
接下来将主要分析ObjectPool的实现类GenericObjectPool, 然后从代码的层面上阐述两者的关联和区别, 最后说明一下对象池的适用场景和不适用场景
GenericObjectPool源码分析
本文基于common-pools 1.6版本的GenericObjectPool Java Source版本分析
-
类注释, 在文件的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
- GenericObjectPool提供了一些可配置的参数如下
-
构造函数
- PoolableObjectFactory
factory, - int maxActive,
- byte whenExhaustedAction,
- long maxWait,
- int maxIdle,
- int minIdle,
- boolean testOnBorrow,
- boolean testOnReturn,
- long timeBetweenEvictionRunsMillis,
- int numTestsPerEvictionRun,
- long minEvictableIdleTimeMillis,
- boolean testWhileIdle
- PoolableObjectFactory
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线程, 继续执行操作.
- 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++; 并把这个对象归还到线程池中
- 在调用wait期间可能会被其他函数中断, 中断善后处理如下, 处理完成之后, 会调用当前线程的interrupt函数, 抛出InterruptedException, 参考Java 理论与实践: 处理 InterruptedException
- 若正常等待完成之后(可能是超时), 检查状态, 可能还会抛出NoSuchElementException
- 按照maxWait的配置, 等待(无线等待或者有限等待)
- 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函数都可以理解