一、什么是线程池
程序中常常使用多线程的方式充分利用CPU的多核特性,但是线程过多会带来额外的开销,比如创建、销毁线程的开销,调度线程的开销等,给系统带来了额外的问题。线程池通过维护一组线程池,给线程分配任务并执行,从而避免了线程的创建、销毁开销,和线程无限制创建带来的调度和内存占用问题。
线程池的优势:
-
线程池维护了一组线程,这些线程可通过拉取任务的方式执行任务,从而达到了复用的效果;
-
任务达到时,线程池将任务分配给对应线程执行,从而提高任务执行的效率;
-
提高了对线程的管理,线程池中不会无限制创建线程。
对于线程池,我们主要关心以下两点:
1、当任务到来时,任务是如果被执行的,是直接创建线程并执行,还是先缓存在任务队列中执行;线程是如何起到复用的效果的。
2、线程池生命周期是什么,线程shutdown()和shutdownNow()二者的区别是什么。
java中通过ThreadPoolExcecutor类提供线程池能力,本文以ThreadPoolExecutor作为入口分析java线程池的原理,并对以上两个问题进行解答。
二、线程池构造方法
ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
}
勘误:当线程池数量达到coresize之后,将任务丢到队列中,如果队列满,而且当前线程池数量 < maximumPoolSize,则创建线程执行任务,否则执行拒绝策略。
三、 任务执行机制
3.1 任务提交
ThreadPoolExecutor父类AbstractExecutorService定义了submit(Runnable task)的实现方式。
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
//将task封装成RunnableFuture,由execute(ftask)执行。
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
void execute(Runnable command)由ThreadPoolExecutor实现:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果线程池线程数量小于corePoolSize,新建Worker(新建线程),执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//否则,如果当前线程池的状态是RUNNING,则尝试在阻塞队列中加入任务
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果线程池的状态不是RUNNING,从阻塞队列中移除该任务
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//如果此时线程池中线程数量为0,则新建worker,从阻塞队列中捞任务并执行。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果线程池数量>corePoolSize而且阻塞队列满 || 线程池的状态非RUNNING,不再接受新提交任务
//执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}
流程图如下:
3.2 任务缓冲
当corePoolSize < 线程数量 < maximumPoolSize时,新提交的任务会被缓存到阻塞队列中,线程池中的线程会从阻塞队列中获取任务,当获取不到任务的时候,会阻塞线程。
private final BlockingQueue<Runnable> workQueue;
常用的阻塞队列实现:
3.3 添加Worker
ThreadPoolExecutor通过内部类Worker控制线程的执行,Worker实现了AbstractQueuedSynchronizer和Runnable,Worker是独占、不可重入的锁。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
//线程
final Thread thread;
//初始化时的task
Runnable firstTask;
//执行成功的任务数量
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//独占不可重入的锁获取操作。
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;
}
}
下面介绍addWorker的逻辑,addWorker()完成的操作主要是创建线程执行任务,如果firstTask为null,则从workQueue中捞取任务执行。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果线程池状态为SHUTDOWN、STOP、TYDING、TERMINATED,不允许新提交任务,直接返回
// 如果状态为SHUTDOWN,如果阻塞队列不为空,而且firstTask为null,表示可以新建线程执行
// 阻塞队列中的任务;或者线程池的状态为RUNNING
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//线程池的数量不能超过阈值,该阈值由调动addWorker()的时机决定。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//增加线程数量
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//如果线程池的状态改变了,重新开始循环
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//此时线程池的数量已经加1
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 {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
//检查线程池的状态
int rs = runStateOf(ctl.get());
//如果线程池状态为RUNNING,或者为SHUTDOWN && firstTask为null,可以新建worker
//执行任务。
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();
}
//如果workder被成功添加,调用线程的start(),执行任务。
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
流程图如下:
执行流程:
- 如果线程池状态 =RUNNING, 线程池可以创建worker执行firstTask 或者线程池状态=SHUTDOWN,&& firstTask=null && workQueue isNotEmpty,表示可以从任务队列中获取任务执行;否则,不能创建worker;
- 当前线程池中的线程数量是否超过阈值,如果超过阈值,则不允许创建worker执行任务:阈值由入参core决定,core为true,表示当前线程数量<corePoolSize,阈值为corePoolSize;否则,表示当前线程数量>=corePoolSize,阈值为maximumPoolSize;
- 获取线程池的全局锁,创建worker,加入到线程池中,启动线程,执行任务;如果线程启动失败,则调用addWorkerFailed()完成回滚、尝试关闭线程池等操作。
3.4 Worker执行
线程运行会调用Worker.run()方法:
public void run() {
runWorker(this);
}
runWorker(Worker woker):
final void runWorker(Worker w) {
//获取到当前线程
Thread wt = Thread.currentThread();
//拿到Worker创建时的task
Runnable task = w.firstTask;
w.firstTask = null;
//worker创建是的同步状态state=-1,调用此方法确保state=0,state>=0说明
//worker已经进入了runWorker()开始运行了,只有进入了runWorker()时才有被中断的意义
//interruptIfStarted()只中断state>=0的worker;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果task为空,则从任务队列中获取队列,getTask()通过抛出异常的方式响应中断
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
//确保线程池的状态在STOP之前,线程没有中断置位;确保线程池的状态>=STOP,线程中断
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;
//worker完成执行的任务数+1
w.completedTasks++;
//放弃独占锁,表示线程此时可以被中断
w.unlock();
}
}
//从以上代码中看出,只有当task.run()执行时抛异常,也就是外部业务代码抛了异常,这行代码
//才不会执行,也就是说completedAbruptly=true表示是由于业务代码执行异常。
completedAbruptly = false;
} finally {
//如果是因为用户的业务代码抛出异常,线程数量-1,剔除该线程;
//否则,回收当前线程,并判断是否需要新建替代线程。
processWorkerExit(w, completedAbruptly);
}
}
线程通过runWorker()中循环调用getTask(),起到了线程复用的功能。
3.5 任务获取
ThreadPoolExecutor.getTask():
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 当线程池状态>=SHUTDOWN时,只有状态为SHUTDOWN而且任务队列不为空,才允许执行任务 //也才能从任务队列中获取任务。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//线程池线程数量-1
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 线程是否允许因为超时被回收;1、allowCoreThreadTimeOut=true说明核心线程超时可以被回收;
// 2、wc > corePoolSize,当前线程数量>核心线程数,也能回收。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果线程数量超过maximumPoolSize || 超时,退出任务的获取,并减少线程数量
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//从workQueue中获取任务,
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
//因为超时未获取到任务,r=null,timeOut=true用于标识超时
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
流程图如下:
3.6 线程退出
processWorkerExit()方法主要是处理线程退出任务执行后,执行的逻辑。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果是执行过程中,因为业务代码抛异常,线程数量-1.
if (completedAbruptly) //
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
//获取线程池的锁
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
//移除woker
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试terminate线程池。
tryTerminate();
int c = ctl.get();
//如果线程池的状态为RUNNING或者SHUTDOWN
if (runStateLessThan(c, STOP)) {
//如果是因为用户导致的退出,不用新建线程。
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//判断当前线程数量是否满足最低要求,如果核心线程允许超时,则表示线程池最少应该有1个线程;
// 否则,corePoolSize是线程池的最低线程数
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//添加worker,task=null,从任务队列中获取任务执行
addWorker(null, false);
}
}
completedAbruptly:true表示因为用户代码执行的异常导致的退出,false表示线程是因为获取阻塞队列中的任务失败导致的退出。
流程图如下:
当线程池中的线程执行到processWorkerExit()时,说明线程要么是因为用户代码抛出异常,或者线程从workQueue里面获取task为null,导致线程退出。对于第一种情况,线程因为用户代码的问题退出,不需要再向线程池中添加替代线程;第二种情况,则要根据当前线程池需要的最低线程数,判断是否需要新建线程。通过tryTerminate()的执行,尝试去关闭线程池。
四、 线程池的生命周期
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;
// 线程池的几种状态
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;
// 返回线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 返回线程池的线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
ThreadPoolExecutor使用原子类AtomicInteger表示线程池的生命周期,其中高3位表示线程池的状态,对应的含义如下:
五种状态的流转关系:
ThreadPoolExecutor提供了两种关闭线程池的方法:
注:shutdownNow()会尝试停止所有正在执行的线程,但是不会保证线程立马停止,而是调用Thread.interrupt(),只有线程调用了wait()、sleep()、获取响应中断的锁等操作时,才会通过InterruptException的方式响应,否则线程会将任务执行完毕后才停止。
4.1 shutdown()原理
public void shutdown() {
//获取线程池的锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查调用者是否有权限shutdown线程池
checkShutdownAccess();
//将线程池的状态设置为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断空闲的线程,使这些线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//尝试关闭线程池
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//如果线程没有被中断 && 线程没有正在执行任务。
if (!t.isInterrupted() && w.tryLock()) {
try {
//中断线程
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
在3.3节中,runWoker()方法在执行时,如果线程获取到了任务,会调用w.lock(),用于标识该线程正在执行任务,这样如果w.tryLock()返回false则说明线程处于空闲中,可以中断。调用interruptIdleWorkers()方法的意义是让线程从在getTask() 中pool/task的阻塞中退出去,重新开始循环获取任务,让该线程能感知到线程池的改变,如果此时线程池状态为SHUTDOWN,而且workQueue为empty,则退出任务的获取,执行线程的退出逻辑。
存在一种情况,当执行完interruptIdleWorkers()后,线程池中可能存在正在执行任务的线程,当这些线程执行任务后,继续循环调用getTask(),如果workQueue.isEmpty,线程会一直阻塞在queue.take()中,导致线程无法退出。对于这部分线程的退出,java设计者采用的方法是调用tryTerminate()。
tryTerminate()方法会尝试将线程池的状态设置为TERMINATED。
-
线程如果处于RUNNING ,或者线程状态为SHUTDOWN 而且任务队列不为空,说明线程池还有任务,则不能讲线程池的状态设置为TERMINATED,返回;或者线程池的状态>=TIDYING,说明线程池正在被其他调用者terminated中,返回;
-
如果线程池中的线程数量> 0,则尝试中断一个空闲的线程;
-
获取线程池的锁,尝试将线程池的状态设置为TYDING,如果成功,则最终将线程池的状态设置为TERMINATED。
final void tryTerminate() { for (;;) { int c = ctl.get(); //如果线程池状态为RUNNING,或者线程池状态>=TYDING,或者线程池状态为 //SHUTDOWN,但是任务队列不为空,不能关闭线程池。 if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; //执行到此处,说明线程池状态=SHUTDOWN && 任务队列中空;或者线程池状态=STOP if (workerCountOf(c) != 0) { // Eligible to terminate //中断一个处于getTask()阻塞中的线程, 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(); } // else retry on failed CAS } }
这个方法中比较晦涩难懂的是为什么需要调用interruptIdleWorkers(ONLY_ONE);
答案:如果方法执行到此处,说明线程池的状态是STOP || (SHUTDOWN && workQueue isEmpty),而且此时线程数量>1,这就说明线程池本身没有任务可执行了,但是线程被阻塞在getTask() queue.take()中,通过调用interruptIdleWorkers(true),会将一个阻塞的线程唤醒退出getTask(),然后调用runWorker()中的processWorkerExit(w, completedAbruptly)方法,执行线程的退出逻辑。在processWorkerExit()中可能会通过调用addWorker(null, false)新创建一个worker,但是这个worker会在getTask()中因为线程池的状态以及workQueue isEmpty,最终执行线程的退出逻辑。processWorkerExit()的执行中,还会调用tryTerminate(),继续之前的步骤,最终线程池中阻塞在getTask()的所有线程都会被唤醒,退出getTask(),最终被回收。
另一个问题,为什么Worker要设计为不可重入?
Worker可以通过调用setCoreSize(size)方法动态调整线程池的核心线程数量:
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) {
// We don't really know how many new threads are "needed".
// As a heuristic, prestart enough new workers (up to new
// core size) to handle the current number of tasks in
// queue, but stop if queue becomes empty while doing so.
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
该方法内部调用interruptIdleWorkers(),通过tryLock()的方式获取空闲的线程,然后中断这些线程。如果AQS可重入,那么worker线程被自身中断,导致worker线程被错误回收,这显然是不被允许的。为了解决这种边界问题,Worker被设计成不支持线程重入的AQS。
4.2 shutdownNow()逻辑
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//设置线程池的状态为STOP
advanceRunState(STOP);
//中断所有线程
interruptWorkers();
//获取workQueue中的所有任务,退出队列
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//尝试关闭线程池
tryTerminate();
return tasks;
}
五、线程池核心参数的设置
假设一个任务的执行时间是t,每秒的任务达到数是100-1000,80%的时间中每秒到达的任务数量在200以内。
5.1 coreSize:
当线程数量小于coreSize时,当一个任务达到时,线程池会创建线程立即执行该任务,核心线程应该能处理80%的流量情况。计算方法:
coreSize=200/(1/0.1)=20,意思是1个线程1s可以处理10个任务,那么200个任务,则需要20个核心线程。
5.2 queueSize
queueSize与coreSize和任务的最大响应时间有关。假设系统要求一个任务最多1s执行完毕,那么queueSize的计算方法:
1000-queueSize
参考:美团点评技术文章:Java线程池实现原理及其在美团业务中的实践