为了弄懂线程池中的线程复用原理,详读了下面资料:
- https://mp.weixin.qq.com/s/jISHo8-aKMPjjeCYGJILgg Java线程池线程复用的秘密
- https://blog.youkuaiyun.com/u011531613/article/details/61921473 Java并发编程:线程池的使用(非常棒,通俗易懂)
- https://blog.youkuaiyun.com/hotdust/article/details/64905254 ThreadPoolExecutor是如何做到线程重用的
通俗版解释:
前提条件:假如coreSize=3,maxSize=10,当前存在线程数是5。
(注意,存在的这5个线程,并不是你执行ExecuteService.execute/submit时的参数,而是为了执行execute/submit的参数所启动的“内部线程”。这个“内部线程”其实是通过ThreadPoolExecutor的ThreadFactory参数生成的线程,而“execute/submit的参数”是执行在这些“内部线程”里面的。)存在这5个“内部线程”,都访问同一个队列,从队列中去取任务执行(任务就是通过execute/submit提交的Runnable参数),当任务充足时,5个“内部线程”都持续执行。重点是没有任务时怎么办?
没有任务时,这5个“内部线程”都会做下面判断:
- 如果poolSize > coreSize,那就从队列里取任务,当过了keepaliveTime这么长时间还没有得到任务的话,当前这个“内部线程”就会结束(使用的是BlockingQueue.poll方法)。
- 如果poolSize <= coreSize,那就以“阻塞”的方式,去从队列里取任务,当得到任务后,就继续执行。这样的话,这个线程就不会结束掉。如果没有任务可以继续执行了,最后只剩下coreSize那么多的“内部线程”留在线程池里,等待重用。
上代码解释(我认为线程复用的关键点):
在线程池中都是以addworker(Worker w)的方法来创建线程的,可以将Work类就理解为线程的封装,worker()构造方法传进来的是runnable对象,直接看worker类的实现
private final class Worker implements Runnable {
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
volatile long completedTasks;
Thread thread;
//构造 函数中传入的任务,是runnable类型
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
boolean isActive() {
return runLock.isLocked();
}
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) {
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
void interruptNow() {
thread.interrupt();
}
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
if (runState < STOP &&
Thread.interrupted() &&
runState >= STOP)
boolean ran = false;
beforeExecute(thread, task); //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
//自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等
try {
task.run();
ran = true;
afterExecute(task, null);
++completedTasks;
} catch (RuntimeException ex) {
if (!ran)
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this); //当任务队列中没有任务时,进行清理工作
}
}
}
它实际上实现了Runnable接口,因此上面的Thread t = threadFactory.newThread(w);效果跟下面这句的效果基本一样:
Thread t = new Thread(w);
相当于传进去了一个Runnable任务,在线程t中执行这个Runnable。
既然Worker实现了Runnable接口,那么自然最核心的方法便是run()方法 (线程复用的核心应该就是Worker类中的Run()方法中的while循环中的判断条件,以及getTask()、和task=null的步骤)
public void run() {
try {
//第一次创建Worker的时候是因为传入了task
//所以此时task赋值为firstTask,其必定不可能为空
Runnable task = firstTask;
firstTask = null;
//while循环,会一直执行,重点是执行或判断的后半部分,去队列中取task执行,线程复用
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;//runtask方法中 afterExecute(task, null)返回,传入任务执行完毕,将task置为null
//while判断语句就会执行后半部分,去队列首部取task,
//取到task,worker线程继续执行,线程复用成功;没取到,blockingqueue的特性使得worker线程阻塞,处于空闲,计时死亡时间
}
} finally {
workerDone(this);//无任务可做、或者shutdown,结束worker
}
}
从run方法的实现可以看出,它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去取新的任务来执行,那么去哪里取呢?自然是从任务缓存队列里面去取,getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法,下面是getTask方法的实现:(worker的退出是在gettask方法中被调用的)
Runnable getTask() {
for (;;) {
try {
int state = runState;
if (state > SHUTDOWN)
return null;
Runnable r;
if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll();
else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
//则通过poll取任务,若等待一定的时间取不到任务,则返回null
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);//允许的空闲时长
else
r = workQueue.take();
if (r != null)
return r;
if (workerCanExit()) { //如果没取到任务,即r为null,则判断当前的worker是否可以退出
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers(); //中断处于空闲状态的worker
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}
在getTask中,先判断当前线程池状态,如果runState大于SHUTDOWN(即为STOP或者TERMINATED),则直接返回null。
如果runState为SHUTDOWN或者RUNNING,则从任务缓存队列取任务。
如果当前线程池的线程数大于核心池大小corePoolSize或者允许为核心池中的线程设置空闲存活时间,则调用poll(time,timeUnit)来取任务,这个方法会等待一定的时间,如果取不到任务就返回null。
然后判断取到的任务r是否为null,为null则通过调用workerCanExit()方法来判断当前worker是否可以退出
private boolean workerCanExit() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
boolean canExit;
//如果runState大于等于STOP,或者任务缓存队列为空了
//或者 允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
try {
canExit = runState >= STOP ||
workQueue.isEmpty() ||
(allowCoreThreadTimeOut &&
poolSize > Math.max(1, corePoolSize));
} finally {
mainLock.unlock();
}
return canExit;
}
也就是说如果线程池处于STOP状态、或者任务队列已为空或者允许为核心池线程设置空闲存活时间并且线程数大于1时,允许worker退出。如果允许worker退出,则调用interruptIdleWorkers()中断处于空闲状态的worker。