本日讲一个牛逼而实用的观念,串行线程关闭。工具池是串行线程关闭的典范应用场景;线程池糅合了工具池技能,但焦点实现不依赖于工具池,很容易发生误会。本文从串行线程关闭和工具池入手,最后通过源码阐明线程池的焦点道理,厘清工具池与线程池之间的误会。
线程关闭与串行线程关闭
线程关闭
线程关闭是一种常见的线程安详设计计策:仅在牢靠的一个线程内会见工具,差池其他线程共享。
利用线程关闭技能,工具O始终只对一个线程T1可见,“单线程”中自然不存在线程安详的问题。
ThreadLocal是常用的线程安详东西,见ThreadLocal的实现道理。线程关闭在Servlet及高层的web框架Spring等中应用不少。
串行线程关闭
线程关闭固然好用,却限制了工具的共享。串行线程关闭改造了这一点:工具O只能由单个线程T1拥有,但可以通过安详的宣布工具O来转移O的所有权;在转移所有权后,也只有另一个线程T2能得到这个O的所有权,而且宣布O的T1不会再会见O。
所谓“所有权”,软件开发,指修改工具的权利。
相对付线程关闭,串行线程关闭使得任意时刻,最多仅有一个线程拥有工具的所有权。虽然,这不是绝对的,只要线程T1事实不会再修改工具O,那么就相当于仅有T2拥有工具的所有权。串行线层关闭让工具变得可以共享(固然只能串行的拥有所有权),机动性获得大大提高;相对的,要共享工具就涉及安详宣布的问题,依靠BlockingQueue等同步东西很容易实现这一点。
工具池是串行线程关闭的经典应用场景,如数据库毗连池等。
工具池
工具池操作了串行关闭:将工具O“借给”一个请求线程T1,T1利用完再交还给工具池,并担保“未擅自宣布该工具”且“今后不再利用”;工具池收回O后,等T2来借的时候再把它借给T2,完成工具所有权的通报。
猴子撸了一个简化版的线程池,用户只需要覆写newObject()要领:
public abstract class AbstractObjectPool<T> {
protected final int min;
protected final int max;
protected final List<T> usings = new LinkedList<>();
protected final List<T> buffer = new LinkedList<>();
private volatile boolean inited = false;
public AbstractObjectPool(int min, int max) {
this.min = min;
this.max = max;
if (this.min < 0 || this.min > this.max) {
throw new IllegalArgumentException(String.format(
"need 0 <= min <= max <= Integer.MAX_VALUE, given min: %s, max: %s", this.min, this.max));
}
}
public void init() {
for (int i = 0; i < min; i++) {
buffer.add(newObject());
}
inited = true;
}
protected void checkInited() {
if (!inited) {
throw new IllegalStateException("not inited");
}
}
abstract protected T newObject();
public synchronized T getObject() {
checkInited();
if (usings.size() == max) {
return null;
}
if (buffer.size() == 0) {
T newObj = newObject();
usings.add(newObj);
return newObj;
}
T oldObj = buffer.remove(0);
usings.add(oldObj);
return oldObj;
}
public synchronized void freeObject(T obj) {
checkInited();
if (!usings.contains(obj)) {
throw new IllegalArgumentException(String.format("obj not in using queue: %s", obj));
}
usings.remove(usings.indexOf(obj));
buffer.add(obj);
}
}
AbstractObjectPool具有以下特性:
固然很简朴,但大可以用于一些时间敏感、资源丰裕的场景。假如时间进一步敏感,可将getObject()、freeObject()改写为并发水平更高的版本,但记得担保安详宣布安详接纳;假如资源不那么丰裕,可以适当增加工具接纳计策。
可以看到,一个工具池的根基行为包罗:
典范的工具池有各类毗连池、常量池等,应用很是多,模子也大同小异,不做理会。令人疑惑的是线程池,很容易让人误觉得线程池的焦点道理也是工具池,下面来追一遍源码。
线程池
首先摆出结论:线程池糅合了工具池模子,但焦点道理是出产者-消费者模子。
担任布局如下:
用户可以将Runnable(或Callables)实例提交给线程池,线程池会异步执行该任务,返反响应的功效(完成/返回值)。
猴子最喜欢的是submit(Callable<T> task)要领。我们从该要领入手,慢慢深入函数栈,探究线程池的实现道理。
submit()