1、线程池的基本工作原理
在java中为了提高线程的利用率提出了java线程池的概念,讲一些线程放入线程池中统一管理调配,使用者需要线程来执行任务时将任务(实现了Runnable接口或Callable接口的类,区别在于是否会有返回值)提交给线程池,线程池就会将任务分配给管理的线程执行,从而使用者不用再关心线程的创建和调度,并且池中的线程可以循环使用执行不同的任务,减小了线程创建与销毁的消耗(线程的创建需要内核态和用户态的切换)。
java中具体的线程池类为ThreadPoolExecutor,创建一个ThreadPoolExecutor构造需要几个关键的参数:核心线程池数、最大线程池数、阻塞队列(用于存放等待执行的任务)、线程创建工厂(可以通过工厂让线程池创建自定义的工作线程)、空闲线程最大存活时间(如果线程池没有任务,线程空等待的最大时间)、时间单位以及拒绝策略(当任务提交超过线程池最大量时如何处理新提交的任务)。如我们平常使用的Executors.newFixedThreadPool()等创建不同类型线程池的方法其底层都是调用该构造方法,只是传入的参数不同使其有不同的特性。
当提交一个任务其基本执行流程如下:
1.判断当前线程数目是否超过核心线程池数,若没有,创建一个新线程执行该任务,若超过,执行下一步。
2.尝试将任务加入阻塞队列,若成功,等待有执行完的空闲线程从队列中取出任务执行,若失败,执行下一步。
3.判断当前线程数目是否超过最大线程池数,若没有,创建一个新线程执行该任务,若超过,执行拒绝策略。
其实现代码如下:
//ctl是线程池一个AtomicInteger变量,用于记录线程池状态和工作线程数量
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //第一步,尝试创建核心线程
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //第二步,尝试加入工作队列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false)) //第三步,尝试创建普通线程
reject(command); //执行拒绝策略
2、线程池源码分析
1.线程池状态变化
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
上文说到的ctl,其低位存放线程数目,高位存放线程池状态,总共有5种状态:RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED。其具体代表数字大小依次递增,因此会通过其大小的判断来执行不同的处理逻辑。
正常运转的线程池处于RUNNING状态,当调用shutdown方法时会转为SHUTDOWN,处于该状态下的线程池不会接收新任务,但会执行完当前正在进行的任务和工作队列中的任务。而调用shutdownNow方法会从RUNNING转为STOP状态,此时线程池不会接收新任务,并且会丢弃工作队列中的任务,并尝试使用interupt中断正在执行的任务(只是尝试,因为interrupt并不能中断正常执行的线程)。当处于SHUTDOWN或STOP状态的线程池的线程执行完最后的任务转为空闲时,线程池就会转为TIDYING状态,该状态时一个过渡状态,此时会调用一个terminate()方法,默认为空,可经过继承自定义,然后转为TERMINATED状态。
2.工作线程执行流程
了解了线程池的构成和状态就可以分析工作线程的执行流程了,其逻辑在线程池的runWorker()方法:
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
//创建工作线程会传递其第一个任务task,如果执行过为空就尝试getTask方法从工作队列中取任务
//通过取任务的循环条件,使得只要任务不为空,线程就不会退出循环消亡。
.....中间的执行Runnable部分省略,其行为控制主要是getTask方法
} finally {
processWorkerExit(w, completedAbruptly);
}
getTask():
boolean timedOut = false; //获取任务是否超时
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); //获取线程池状态
// 如果状态为SHUTDOWN且工作队列为空或者状态为STOP,直接返回空,线程结束。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c); //获取线程数目
// 判断是否运行线程超时消亡
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果线程数目大于最大线程池数或者获取超时,允许返回null使线程消亡
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
//从阻塞队列尝试以一定时间获取任务,利用阻塞队列的性质使线程等待
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true; 获取为空说明超时,在下一次循环中可能因为超时在上面的逻辑中退出
} catch (InterruptedException retry) {
timedOut = false;
}
}
综上,getTask会对线程池状态进行判断以及利用阻塞队列的阻塞获取使空闲线程保证一定的空闲存活时间,当不满足条件时就会返回null使线程退出循环而终止。
在runWorker方法中我们还看到在finally中有个processWorkerExit()方法,该方法中定义了一些线程的收尾工作,其中主要一段代码保证了核心线程的不减少(是不减少而不是不消亡):
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
如上,如果是在不允许核心线程消亡的状态下,会向线程池加入一个空任务的工作线程来弥补这个线程的消亡,从而使线程在数量未超过核心线程池数的时候不会减少。(实际上核心线程和普通线程都是一样的worker线程,只是根据线程数与核心线程池数的大小进行了划分来管理)