在开发中,我们常常会通过使用线程来完成一些需要并发执行的子任务,但是如果这些并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么怎样才能使得一个线程在执行完任务之后不被销毁,继续执行下一个任务,从而实现线程的复用呢?在Java中我们可以通过线程池来达到这样的效果。
一、为什么要使用线程池
使用线程池大概有以下几个原因:
1.提高程序的效率。由于创建线程和销毁线程都要占据系统资源,而线程池中的线程是可以实现复用的,我们不需要每次得到一个任务的时候就去创建一个新的线程来执行任务,而是使用已经存在于线程池中的线程来完成任务。这样可以节约大量的创建/销毁线程造成的系统开销;
2.可以有效地对系统资源进行管理。我们都知道多线程环境下,系统资源是由许多线程共同竞争的,这样一来如果系统中的线程过多而系统资源是有限的,在资源竞争的过程中可能会形成阻塞。而线程池可以有效的控制并发线程的最大数量,从而避免阻塞。
3.可以对线程进行一些管理和设置。如实现线程的延时执行、定时执行、循环执行等等。
二、ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器。事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
1.corePoolSize:核心线程池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思。即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
2.maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
3.keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
4.unit:参数keepAliveTime的时间单位;
5.workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有这几种选择:ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingQueue、SynchronousQueue。ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关;
6.threadFactory:线程工厂,主要用来创建线程;
7.handler:表示当拒绝处理任务时的策略,详细参数配置会在下面讲解。
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等等;抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;最后,ThreadPoolExecutor继承了类AbstractExecutorService。
至此,我们已经梳理了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
在ThreadPoolExecutor类中有几个非常重要的方法:submit()、execute()、shutdown()、shutdownNow()
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
shutdown()和shutdownNow()是用来关闭线程池的。其中shutdown()表示待当前的任务全部执行完毕后关闭线程组;shutdownNow()表示立即关闭线程组,并且停止正在执行的任务。
还有其他的方法,比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,在此不深入讨论。
三、深入解析线程池实现原理
上一节简单介绍了ThreadPoolExecutor,下面来深入解析线程池的具体实现原理。
1、线程池状态
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
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;
上面的static final变量表示runState可能的几个取值:
- runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
- RUNNING:当创建线程池后,初始时,线程池处于RUNNING状态;
- SHUTDOWN:如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
- STOP:如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
- TIDYING:当线程池所有tasks都terminated,并且线程数为0的状态,该状态下会调用terminated()方法,向terminated状态转变。
- 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
先解释一下每个状态的值的计算方法:
首先ctl变量表示线程池的状态,它是一个32位的AtomicInteger原子型变量,其中最高三位的值用来表示线程池状态runState,其余29位用来表示线程池内的任务数量workerCount,也就是说最多可以有2^29=536870912个任务。COUNT_BITS常量等于29,CAPACITY常量为1左移29位之后减1((1<<COUNT_BITS)-1)也就是29位二进制数所能表示的最大值。
然后就是给每个状态常量赋值了,拿RUNNING举例,其值为-1<<COUNT_BITS,得到的结果是1110 0000 0000 0000 0000 0000 0000 0000,其中的前三位111是补码表示的-1(不清楚补码规则的话点这里),以此类推SHUTDOWN = 0000 0000 0000 0000 0000 0000 0000 0000,STOP = 0010 0000 0000 0000 0000 0000 0000 0000。
这里特别解释一下变量ctl,因为是一个AtomicInteger原子型变量,所以它可以保证线程池状态在多线程环境下的线程安全。ctl是由ctlOf()方法得到的,参阅官方文档:
* The main pool control state, ctl, is an atomic integer packing * two conceptual fields * workerCount, indicating the effective number of threads * runState, indicating whether running, shutting down etc
可知ctl是由两部分组装而成,一个是workerCount也就是任务数量,另一个是runState即线程池的当前状态。ctlOf()方法如下:
private static int ctlOf(int rs, int wc) { return rs | wc; }
ctlOf()接收两个状态的值作为参数,其中的 rs | wc 表示将两个变量按位做或运算,也就是有1得1、没有1得0。比如runState=1110 0000 0000 0000 0000 0000 0000 0000,workerCount=0000 0000 0000 0000 0000 0000 0000 0000这样计算下来就将两个状态变量拼装成一个ctl值,这个ctl的值为1110 0000 0000 0000 0000 0000 0000 0000(高三位为-1表示RUNNING状态,后29位为0表示没有任务)。初始化ctl时ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 即ctl初值是上述值。
接下来有两个关键的方法
private static int runStateOf(int c) { return c & ~CAPACITY; } //获取线程池状态
private static int workerCountOf(int c) { return c & CAPACITY; } //获取任务数
runStateOf()将c(c = ctl.get())和CAPACITY按位取反后的结果再进行与运算(有0得0,全1得1),计算结果为除高三位不变低位全部为0。同样workerCountOf()的结果为高三位为0,低位不变。
2、任务的执行
在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
private final BlockingQueue<Runnable> workQueue;//线程池任务队列,用于存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();//用于线程池一些统计信息更新时的锁,e.g.如下的largestPoolSize,complectedTaskNum,ctl的状态和线程数更新时。
private final HashSet<Worker> workers = new HashSet<Worker>();//线程池工作集,Runnable的实现,里面封装了thread变量成员,用于执行任务
private final Condition termination = mainLock.newCondition();//客户端调用awaitTerminate()时,将阻塞与此,线程池处于terminate后,会调用condition.signalAll()通知(有time参数的另外考虑)。
private int largestPoolSize;//用于记录线程池运行过程曾出现过得最大线程数,不同于maxiumPoolSize
private long completedTaskCount; //用于动态记录线程池完成的任务数
private volatile ThreadFactory threadFactory;//用于创建新的线程,newThread(Runnable).
private volatile RejectedExecutionHandler handler;//拒绝任务提交的处理器
private volatile long keepAliveTime;//当线程数大于corePoolsize时,多出那部分线程处于idle状态时,最大保活时间。
private volatile boolean allowCoreThreadTimeOut;//当线程数小于corePoolsize时,是否允许其也运用保活时间限制。
private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize;//线程池运行的最大线程数。
下面通过一个例子来解释一下corePoolSize、maximumPoolSize这两个变量,也是因为一开始我没有理解为什么要存在两个表示线程池大小的变量。
假设一个工地相当于一个线程池,工地上有30名工人,每当工地上有任务的时候就安排工人去完成,30个工人就相当于corePoolSize,他们属于正式工(核心线程);突然有一天,上级要求缩短工期,提前完成建设项目,这就意味着每天的工作量增加了(额外任务放入workQueue),工人们需要先完成每天应该完成的任务,然后加班继续完成额外的任务(workQueue中的任务);突然有一天,上级要求再次缩短工期,可是多出来的任务已经远远超过了工人们所能承受的极限,只能招一些零时工到工地上来,但是由于预算问题工地上的总人数又不能超过人数上限(maximumPoolSize),所以如果当人数抵达上限时,还有更多的任务,工地可以采取拒绝策略。当这一批工作量很大的任务执行完后,临时工就被解雇掉了,只留下30名正式工继续完成以后的任务。这里有一点比喻不太恰当的地方是,留下的30名正式工不一定是之前的30人,只是随机解雇多于30人的那部分工人(所以这30人也不是真正的正式工,临时工来了他们可能也会被解雇)。也就是说核心线程和非核心线程是虚拟的概念,如果线程池中的线程多于指定的核心线程数时,就会进行销毁直到数量等于核心线程数,销毁的线程是随机的,线程上并没有标注哪个是核心线程哪个是非核心线程。
接下来我们看一下一个任务从创建到提交到执行经历了什么过程。
在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可:
public void execute(Runnable command) {
if (command == null)
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)) //如果在这之间线程池被shutdown则删除任务
reject(command); //拒绝任务
else if (workerCountOf(recheck) == 0) //若线程池空则添加一个空任务
addWorker(null, false);
}
else if (!addWorker(command, false)) //缓存已满,创建非核心线程
reject(command);
}
execute()方法接收一个Runnable参数,用来向线程池中添加任务。首先判断核心池,如果核心池满则添加到缓存队列,若缓存队列也满则创建新的非核心线程,若线程数达到了最大线程数限制则采用拒绝策略。
在execute()中出现最多的方法应该就是addWorker()了,下面我们就来看一下addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); //在进行“任何动作”之前都先检查两个状态
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//这里逻辑非常绕,总的来说我们讨论一下什么情况下会返回false:
//首先rs >= SHUTDOWN是必须的,否则if后的判断为false;
//其次后面!(...)括号里面一大堆总的来说要分三个情况讨论,要保证!()的值为1就要
//保证括号内的值为0,所以只要()里的任一个表达式结果为0即可;
//1.rs != SHUTDOWN 即此时状态为stop,terminate,tidiy 返回false,不添加新线程
//2.firstTask != null 即此时rs >= SHUTDOWN 即程序调用了shutdown方法,且提交的
//任务为null,则返回false
//3.workQueue.isEmpty() 此时调用了shutdown方法且待执行的任务为null则返回false
for (;;) { //死循环
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false; //任务数大于总数或者大于核心or总线程数
if (compareAndIncrementWorkerCount(c))
break retry; //CAS操作改变任务数并跳出retry循环
c = ctl.get(); // 读取c状态
if (runStateOf(c) != rs)
continue retry; //若状态出错则继续执行循环,线程创建不成功
}
}
//以下表示允许新线程创建,创建新线程并将其添加到线程池
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get()); //检查线程池状态
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize) //记录最大线程数量
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) { //创建并添加成功,启动线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) //若上述过程失败,则执行addWorkerFailed()方法
addWorkerFailed(w);
}
return workerStarted;
}
这里涉及到之前没有使用过的新方法和新的类,如Worker类、addWorkerFailed()方法、compareAndIncrementWorkerCount()方法,下面就来看一下他们是怎么实现的。
首先看Worker类,顾名思义它表示线程池中的任务也就是线程
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread; //运行次任务的线程,工厂创建失败则为null
Runnable firstTask;
volatile long completedTasks;//完成的线任务数
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); //设置状态为-1防止在run之前被interrupt
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this); //运行任务
}
//一些有关锁的方法,其中0代表unlocked,1代表locked
protected boolean isHeldExclusively() {
return getState() != 0;
}
//获取锁
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
//用于shutdownNow,中断当前正在执行的线程(-1状态下不可中断)
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
因为可以看到在run()中调用了runWorker()方法,所以接下来先看一下runWorker的实现
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 释放锁允许被打断
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//上面又是一段很复杂的判断,我们来分析下各种情况(心好累...-_-#,我也不确保我
//到底能不能看懂我自己写的)
//首先弄清楚这个判断是!wt.isInterrupted() && 前面的一大段,所以先看下
//!wt.isInterrupted()的意思,表示wt也就是当前的线程没有被打断
//在看前面的一大段,官方文档中说,如果pool处于stop以上状态,要确保线程被中断过
//如果处于stop一下则要确保线程没有被中断过。
//于是当(runStateAtLeast(ctl.get(),STOP)为true时直接判断线程是不是被中断
//过,如果被中断过则跳出,否则执行interrupt();
//当处于stop以下的状态时,正常执行线程
wt.interrupt();
try {
beforeExecute(wt, task);
//beforeExecute(),afterExecute()可以在task执行前后做一些工作,例如
//统计,日志
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
我们注意到上面的方法中用到了一个getTask()方法,那么这个getTask()方法是用来干嘛的呢?
private Runnable getTask() {
boolean timedOut = false; //用来标志是否等待超时,即是否在keepAlive内没有收到任务
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果队列为空且状态为shutdown或状态为stop以上则减少worker数量
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//核心池允许超时或者线程数大于核心池,则time为true
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//如果time为true,表示当前线程是非核心线程或者是允许超时的核心线程,则选择等待
//keepAliveTime的时间后返回,如果没取到任务就返回null;
//如果time为false则会一直阻塞下去直到等到任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
于是我们知道,当线程池中的Worker不会被销毁时,他会阻塞一直等待新任务的到来。这里有一个巧妙的设计方式,假如我们来设计一个线程池,可能会设置一个管理线程用来监控线程的状态,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,因为这个管理线程无形地会增加难度和复杂度,这里直接让执行完任务的线程自动去任务缓存队列里面取任务来执行。这就好像一个老板每隔一段时间要到工位上看看员工手里的工作有没有完成,如果完成了就分配新的工作,这样老板会十分地累;而如果老板雇佣了一批勤快的员工,每次干完手头的工作,就来找老板要新的任务,老板就轻松许多了。
从刚才进入Worker类开始分析,一层一层的深入现在跑的有点远,我们会到上文还没有解决的addWorkerFailed()方法,看一下它是怎么实现的。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
可以看到addWorkerFailed方法非常简短(我很喜欢~),首先获得mainLock,然后将w从workers中移除,接着减少WorkerCount,一切看起来都是那么简洁顺畅,直到我们又看到了一个陌生的方法tryTerminal()
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
进入tryTerminal()方法中看了一眼,还好不算很长,我们来一步步分析。其实这个方法也没那么复杂,简单来说就是先判断一下能不能将状态设为TERMINAL,一看还在running,不行,直接return;又一看已经处于TIDYING以上状态,不用再手动terminal()了,直接返回;再看发现处于SHUTDOWN并且队列里还有任务,不能terminal,直接return。
成功断定当前线程池可以terminal(),如果还有任务,则开始使用interruptIdleWorkers()清除任务。
任务清除之后就可以执行terminal()了,先使用CAS将状态修改,然后调用terminal()。
到这里,大家应该对任务提交给线程池之后到被执行的整个过程有了一个基本的了解,下面总结一下:
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
- prestartCoreThread():初始化一个核心线程;
- prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
3、任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
4、线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()、setMaximumPoolSize()
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
四、合理配置线程池大小
要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:
- 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
- 任务的优先级:高、中、低。
- 任务的执行时间:长、中、短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接等。
性质不同的任务可以交给不同规模的线程池执行。
对于不同性质的任务来说,CPU密集型任务应配置尽可能少的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。
若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。 当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。
网上有一个公式可以做参考:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
可以得出一个结论: 线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。 以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。
读完ThreadPoolExecutor线程池源码之后,有一个深刻的体会,就是在任何一个类或方法中无不体现着J.U.C作者Doug Lea严谨的思维和各种巧妙的设计,不愧是世界级的大牛,并发编程领域的泰山北斗。阅读源码之后我也是受益良多,明白了什么时候使用原子类、什么时候使用cas方法、什么时候进行相应的锁操作,也明白自己要学的东西还有很多,路还很长...
参考资料:
https://www.cnblogs.com/onlysun/p/4603200.html
https://blog.csdn.net/MingHuang2017/article/details/79571529
https://blog.csdn.net/qq_17058993/article/details/80533162