今天讲一个牛逼而实用的概念,串行线程封闭。对象池是串行线程封闭的典型应用场景;线程池糅合了对象池技术,但核心实现不依赖于对象池,很容易产生误会。本文从串行线程封闭和对象池入手,最后通过源码分析线程池的核心原理,厘清对象池与线程池之间的误会。
线程封闭与串行线程封闭
线程封闭
线程封闭是一种常见的线程安全设计策略:仅在固定的一个线程内访问对象,不对其他线程共享。
使用线程封闭技术,对象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具有以下特性:
- 支持设置最小、最大容量
- 对象一旦申请就不再释放,避免了GC
虽然很简单,但大可以用于一些时间敏感、资源充裕的场景。如果时间进一步敏感,可将getObject()、freeObject()改写为并发程度更高的版本,但记得保证安全发布安全回收;如果资源不那么充裕,可以适当增加对象回收策略。
可以看到,一个对象池的基本行为包括:
- 创建对象newObject()
- 借取对象getObject()
- 归还对象freeObject()
典型的对象池有各种连接池、常量池等,应用非常多,模型也大同小异,不做解析。令人迷惑的是线程池,很容易让人误以为线程池的核心原理也是对象池,下面来追一遍源码。
线程池
首先摆出结论:线程池糅合了对象池模型,但核心原理是生产者-消费者模型。
继承结构如下:
用户可以将Runnable(或Callables)实例提交给线程池,线程池会异步执行该任务,返回响应的结果(完成/返回值)。
猴子最喜欢的是submit(Callable<T> task)方法。我们从该方法入手,逐步深入函数栈,探究线程池的实现原理。
submit()
submit()方法在ExecutorService接口中定义,AbstractExecutorService实现,ThreadPoolExecutor直接继承。
public abstract class AbstractExecutorService implements ExecutorService { ... public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; } ... }
AbstractExecutorService#newTaskFor()创建一个RunnableFuture类型的FutureTask。
核心是execute()方法。
execute()
execute()方法在Executo