创建线程是一个非常耗时的操作,所以一般在系统中都会创建一个线程池,当有需要执行的任务时,直接从线程池中获取一个线程执行。
在java里面,一般使用类ThreadPoolExecutor创建线程池。本文接下来详细介绍ThreadPoolExecutor的原理。
本文目录
- 一、ThreadPoolExecutor的例子
- 二、源码分析
- 1、准备知识
- 2、execute()
- 3、addWorker()
- 4、remove()
- 5、reject()
- 6、Worker类
一、ThreadPoolExecutor的例子
下面先看一个ThreadPoolExecutor的例子。
public class Main {
public static void main(String argv[]){
ThreadPoolExecutor threadPool=new ThreadPoolExecutor(1,2,
10, TimeUnit.MINUTES,new ArrayBlockingQueue(5));
AtomicInteger cnt=new AtomicInteger(0);
Runnable r=()->{System.out.println("启动线程"+cnt.getAndAdd(1));};
threadPool.execute(r);
threadPool.execute(r);
threadPool.execute(r);
}
}
上面代码创建一个核心线程数为1,最大线程数为2的线程池,Runnable表示需要执行的任务,使用ThreadPoolExecutor.execute()方法执行任务。如果线程池中有空闲线程会立即执行任务,如果没有,则将任务放到阻塞队列中。
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) {}
构造方法的各个入参含义如下:
- corePoolSize:核心线程数,这是线程池中维持的最小线程数,如果超过了该数字,那么线程空闲一段时间后会被销毁;
- maximumPoolSize:线程池中所能维持的最大线程数,超过了corePoolSize的线程数属于非核心线程;
- keepAliveTime和unit:如果线程超过了核心数,那么当线程空闲了keepAliveTime指定的时间后,会被销毁,unit表示keepAliveTime的单位;
- workQueue:阻塞队列,如果线程池中所有的核心线程都在运行任务,此时还有任务加入,那么任务会被放入阻塞队列中,待阻塞队列也被放满后,会创建新非核心线程执行任务,核心线程数+非核心线程数<=maximumPoolSize;
- threadFactory:线程工厂,ThreadPoolExecutor内部都是使用threadFactory创建线程,java提供有默认的线程工厂DefaultThreadFactory;
- handler:如果线程池的线程数已经达到了最大线程数,而且所有的线程都在执行任务,阻塞队列也满了,那么ThreadPoolExecutor是无法再增加新任务,新加入的任务都应该拒绝,ThreadPoolExecutor提供了一个RejectedExecutionHandler用于处理这种场景下的新加任务,java提供了四种拒绝策略,也就是RejectedExecutionHandler的四个实现类。
二、源码分析
任务使用Runnable对象表示,调用ThreadPoolExecutor.execute()便可以执行任务,当然ThreadPoolExecutor中也提供了其他的执行任务的方法,但是execute()是最核心的,理解了该方法的逻辑,其他的方法也就很好理解了,下面将重点介绍execute()方法。
1、准备知识
ThreadPoolExecutor使用了一个AtomicInteger类型的属性ctl来表示线程池状态和线程数量,ctl的定义如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ThreadPoolExecutor使用ctl的高3位表示线程池状态,低29位表示线程数量,默认线程池状态是RUNNING,线程数是0。
ThreadPoolExecutor使用如下常量表示线程池的状态:
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int RUNNING = -1 << COUNT_BITS;//高3位为111
private static final int SHUTDOWN = 0 << COUNT_BITS;//高3位为000
private static final int STOP = 1 << COUNT_BITS;//高3位为001
private static final int TIDYING = 2 << COUNT_BITS;//高3位为010
private static final int TERMINATED = 3 << COUNT_BITS;//高3位为011
各个状态的含义如下:
- RUNNING:运行状态,可以接受新任务并且处理队列中的任务,也是默认状态,该状态也是这些状态中唯一一个负数,因此判断线程池状态是否是RUNNING,只需判断是否小于0即可。
- SHUTDOWN:关闭状态(调用了shutdown方法)。不接受新任务,,但是要处理队列中的任务。
- STOP:停止状态(调用了shutdownNow方法)。不接受新任务,也不处理队列中的任务,并且要中断正在处理的任务。
- TIDYING:所有的任务都已终止了,workerCount为0,线程池进入该状态后会调 terminated()方法进入TERMINATED 状态。
- TERMINATED:终止状态,terminated() 方法调用结束后的状态,默认terminated() 是一个空实现。
这些状态的转换关系如下图:
图片引自:
介绍完了准备知识,下面正式进入execute()方法。
2、execute()
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();//获取ctl值
//workerCountOf()方法通过位运算得到线程数
//下面的if判断是如果当前线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//addWorker()的第二个入参表示是否使用核心线程执行任务,true表示使用
//addWorker()里面会创建新线程执行任务
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果线程数超过了核心线程数,或者addWorker()执行失败,那么会进入下面的分支判断
//workQueue就是构造方法中的入参阻塞队列,下面将任务放入到阻塞队列中
if (isRunning(c) && workQueue.offer(command)) {
//任务放入阻塞队列成功后,需要再次检查线程池状态,因为在多线程环境下,线程池的状态随时可以发生变化,
//如果此时线程池关闭了,那么需要将任务从阻塞队列中删除,
//如果此时线程池中没有存活的线程了,那么需要创建一个线程执行阻塞队列中的任务
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
//如果线程池不是运行状态,那么将任务从阻塞队列中删除,
//任务交给RejectedExecutionHandler处理
reject(command);
else if (workerCountOf(recheck) == 0)
//阻塞队列新增任务后,而线程池中的线程数为0,那么需要新建线程执行任务
addWorker(null, false);
}
//如果阻塞队列已满,使用非核心线程执行任务,如果失败,则调用RejectedExecutionHandler处理
else if (!addWorker(command, false))
reject(command);
}
通过execute()方法可以基本知道线程池如何工作的,用下图总结一下execute()的执行流程:
注意:当线程数不超过核心线程数时,每次新来一个任务都会创建一个线程,这样做的目的是尽快使线程池中的线程数达到coreSize。
execute()里面调用了多个方法,下面一一介绍这些方法的执行逻辑。
3、addWorker()
addWorker()根据入参core的不同,判断是使用核心线程处理任务还是使用非核心线程。只要是线程池状态处于RUNNING,且线程池个数没有超过限定值,那么便会新建线程,处理任务,否则该方法返回false。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);//rc表示线程池状态
//如果线程池已经停止,或者已经关闭,那么不会再接收新任务,直接返回false
//如果线程池已经关闭,新任务也是null,且阻塞队列中没有需要执行的任务,该方法直接返回false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//如果线程池的状态检查合法,那么下面开始检查线程数
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
//根据入参core的设置,判断是使用核心线程还是非核心线程
wc >= (core ? corePoolSize : maximumPoolSize))
return false;//如果线程数超过了限定值,直接返回false
if (compareAndIncrementWorkerCount(c))//使用CAS修改线程数
break retry;//线程数修改成功,进入下一步
//如果线程数修改不成功,则再次循环尝试修改
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
}
}
//到这里为止,说明线程池状态合法,线程数也已经修改成功,那么下面便可以新建线程处理任务了
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//Worker里面会新建Thread对象,该类在后面会介绍
//一个Worker对象其实也就是一个工作线程,它负责执行任务
w = new Worker(firstTask);
final Thread t = w.thread;//t表示新建的Thread对象
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();//加锁,这里加锁主要是为了防止其他线程修改线程池状态和workers
try {
int rs = runStateOf(ctl.get());
//如果线程池处于RUNNING,或者不是RUNNING但新任务是null,
//那么将工作线程放入集合中(workers),该集合主要作用是记录当前有多少存活的线程
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(w);
}
return workerStarted;
}
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);//将工作线程从集合中删除
decrementWorkerCount();//减少线程数
tryTerminate();//该方法在下面介绍
} finally {
mainLock.unlock();
}
}
addWorker()的代码很多,但是其逻辑很简单,首先检查线程池状态,然后检查线程数是否超过了限定值,如果两者都没有问题,那么便增加线程数,新建线程并启动线程。如果在启动线程前或者启动过程中出现异常,会调用addWorkerFailed()修改线程数,并检查是否停止线程池。
4、remove()
当线程池处于非运行状态时,那么添加到阻塞队列的任务需要再从阻塞队列删除,删除任务调用remove()。
线程出处于非运行状态,说明已经调用过shutdown()或者shutdownNow()之类的方法,说明需要停止线程池运行。
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);//从阻塞队列删除任务
tryTerminate(); //检查线程池是否要停止,中断空闲线程
return removed;
}
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
//如果线程池处于RUNNING状态,或者处于TIDYING状态,
//或者线程池处于SHUTDOWN但还有任务需要执行,那么该方法不做任何处理,直接返回
return;
// 如果线程池处于SHUTDOWN,且没有任务需要执行,或者处于STOP,那么需要中断空闲线程
if (workerCountOf(c) != 0) {
//这里只中断一个线程,shutdown()方法会中断所有的线程
//我理解这里中断一个线程的原因是为了效率的考虑,因为shutdown()已经开始中断所有的线程了
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//如果没有待执行任务,修改线程状态为TIDYING,修改线程数为0
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();//空方法
} finally {
ctl.set(ctlOf(TERMINATED, 0));//修改线程状态为TERMINATED
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
5、reject()
该方法非常简单,直接将任务交给RejectedExecutionHandler处理了。
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
6、Worker类
到这里,execute()方法的基本处理流程介绍完了,但是还有一个Worker类没有介绍,现在我们知道Worker类里面会创建一个Thread对象并执行任务,但是是怎么执行的,阻塞队列里面的任务什么时候执行?还有一系列的问题没有解决,下面我们深入Worker类。
在addWorker()方法里面,会新建一个Worker对象处理任务,一个Wokrer对象持有一个Thread对象,因此Wokrer也就相当于一个工作线程,下面看一下该类源码:
//代码有删减,Worker继承自AQS,Worker是一个独占锁
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
//持有的线程对象
final Thread thread;
//待执行任务
Runnable firstTask;
//执行任务计数器
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); //设置AQS的属性state值,线程运行前,会将state修改为0
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//创建Thread
}
public void run() {
runWorker(this);//运行任务
}
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); }
//解锁,线程运行前,必须先执行该方法,将state设置为0
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
//中断线程
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
Worker继承自AQS,因此Worker也是一个锁对象,而且是一个独占锁(使用锁的原因的是防止Worker执行任务的过程中被中断)。启动线程后,线程会执行Worker的run()方法,而run()方法又去调用ThreadPoolExecutor.runWorker()方法。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;//firstTask是通过execute()方法传入的,表示第一个需要执行的任务
w.firstTask = null;
w.unlock(); //要想使Worker的锁可用,必须先调用unlock()方法
boolean completedAbruptly = true;
try {
//getTask()可以从阻塞队列里面获取待执行任务
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();//如果线程池一个关闭或者停止,那么中断线程
try {
beforeExecute(wt, 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++;//任务计数器加1
w.unlock();
}
}
completedAbruptly = false;
} finally {
//有两种情况会执行下面的方法,一是执行任务过程中抛出异常,二是任务全部执行完毕退出循环,
//前一种情况入参completedAbruptly为true,后一种情况completedAbruptly=false
//如果是前一种情况,processWorkerExit()会调用addWorker()方法新建一个线程
//如果是后一种情况,根据线程池中的线程个数判断是否新建线程,如果线程池中至少有一个活动线程,那么便不新建,否则调用addWorker()方法新建
processWorkerExit(w, completedAbruptly);
}
}
runWorker()首先执行execute()方法传入的任务,该任务执行完后,调用getTask()方法从阻塞队列里面获取任务继续执行,如果任务全部执行完,或者抛出异常,那么退出执行,进入processWorkerExit()方法。如果是因为异常退出的,那么便新建一个线程继续执行任务,如果是因为任务全部执行完退出的,那么根据线程池中目前的线程个数判断是否新建线程。
总的来看,runWorker()的逻辑还是简单的。
runWorker()里面会调用getTask()方法从阻塞队列里面拿任务,下面再来看一下该方法如何执行的。
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//检查线程池是否已经关闭或者停止了
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();//线程数减1
return null;
}
int wc = workerCountOf(c);//wc表示线程个数
//allowCoreThreadTimeOut默认是false
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果线程数大于最大线程数,或者大于核心线程数且阻塞队列中没有待执行任务,
//那么ThreadPoolExecutor可能会缩减线程数
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;//任务不为null,直接返回
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
总结一下getTask()方法的执行流程:
- 如果当前线程数大于最大线程数,那么该方法直接返回,停止线程运行;
- 如果当前线程数大于核心线程数,且阻塞队列里面没有待执行任务,那么该方法直接返回,停止线程运行;
- 如果当前线程数大于核心线程数,但是阻塞队列里面还有待执行任务,那么使用带超时时间的poll()方法从阻塞队列里面获取新任务;
- 如果当前线程数小于等于核心线程数,那么直接从阻塞队列里面获取待执行任务,如果阻塞队列为空,那么线程会一直阻塞,直到有任务可以执行。