ThreadPoolExecutor
一、概述
线程的创建和销毁是比较耗时的,池的概念都是为了防止对象频繁的创建和销毁,类似的概念还有连接池,或者更通用的叫对象池。但是跟我们对象池的模型不太一样,对象池常用的方法是borrowObject和returnObject。用的时候借出来,不用的时候还回去。数据库连接池也是一样的,对getConnection获取线程,close(并没有真正的close)的时候还回去。
而jdk的ThreadPoolExecutor(线程池),是一个生产者消费者的模型。线程池是一个消费者,而我们不断生成任务,不断的submmit/execute任务。虽然是生产者消费者模型,但是,大体来说,也是一个池的概念。比如有最大/最小线程数,线程的最大空闲时间,线程工厂等等。也有些,生产/消费者模型才有的东西,比如任务队列。
1.1 构造方法
ThreadPoolExecutor有很多构造方法以及构造方式,最后都会调用一个七个参数的方法,参数的含义分别是:
- int corePoolSize: 核心线程数
- int maximumPoolSize: 最大线程数
- long keepAliveTime & TimeUnit unit: 最大空闲时间
- BlockingQueue workQueue: 工作队列,是一个阻塞队列
- ThreadFactory threadFactory: 线程工厂,可以给线程起一个有意义的名字,也可以对线程进行扩展,比如给线程设置UncaughtExceptionHandler;
- RejectedExecutionHandler handler: 拒绝策略,当上面的工作队列,是一个有界队列的时候,如果队列已经满了的场景的执行策略
ThreadPoolExecutor已经提供了四种拒绝策略:
- CallerRunsPolicy:提交任务的线程自己去执行该任务。
- AbortPolicy:默认的拒绝策略,会throws RejectedExecutionException。
- DiscardPolicy:直接丢弃任务,没有任何异常抛出。
- DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列
另外,ThreadPoolExecutor还有一个allowCoreThreadTimeOut(boolean value)方法,这个参数设置为TRUE,corePoolSize的线程空闲超时也会回收。如果线程池长期闲置,可以设置为TRUE。
一般情况下,在初始化的时候,对应的属性就已经指定了,不过,ThreadPoolExecutor也提供了创建后指定属性的方法,这些方法也有对应的get方法。可以利用这些方法实现对线程池的监控及动态修改。
- setCorePoolSize 设置指定核心线程数
- setMaximumPoolSize 设置最大线程数
- setKeepAliveTime 设置最大空闲时间
- setThreadFactory 设置线程工厂
- setRejectedExecutionHandler 设置拒绝策略
对应任务队列只有一个get方法(getQueue),不过这个方法,目的仅仅是为了获取任务队列用于调试或监控,不应该随意更改这个队列。
1.2 线程池中线程的生命周期
1.2.1 线程的创建 prestartCoreThread & prestartAllCoreThreads
线程池中的线程,是延迟初始化的,提交任务才会触发线程的创建。也可以直接调用prestartCoreThread(启动一个核心线程)或者 prestartAllCoreThreads(启动所有核心线程),来提前启动线程。注意如果在ThreadPoolExecutor的构造时,直接使用了不为空的等待队列,则必须调用prestartCoreThread或prestartAllCoreThreads来启动线程,消费任务。
1.2.2 提交任务 execute & submit
当一个任务提交到线程池中:
- 当前运行线程数,少于corePoolSize,即使存在空闲线程,也会创建新的线程,来处理任务;
- 当前运行线程数,大于等于corePoolSize,任务队列没有满,则进入等待队列;
- 当前运行线程数,大于等于corePoolSize,任务队列已满,但是运行线程数小于和maximumPoolSize,创建新线程,处理任务;
- 否则,只能执行拒绝策略了;
1.2.3 线程空闲
当线程池中线程空闲时间(也就是在阻塞队列中等任务的时间)超过设置的空闲时间,如果此时线程数量大于corePoolSize,则线程会退出,否则不会退出。除非调用allowCoreThreadTimeOut(true),使线程数小于等于核心线程数,也会退出。注意,源码中并没有把某个运行的线程标记为核心线程或者非核心线程,而只有线程数量的概念。
1.2.4 线程池的停止 shutdown & shutdownNow
ThreadPoolExecutor提供了两种线程停止的方式
- shutdown 不再接受新的任务,直到所有任务执行完毕,线程退出;
- shutdownNow 不再接受新的任务,删除任务队列中的所有任务,并给所有线程发中断,线程空闲,自然就退出了,正在执行任务,如果任务响应中断,也会退出,否则能任务执行完毕,线程退出。
1.2.5 获取线程状态
ThreadPoolExecutor有几个获取当前状态的方法
- isShutdown 是否shutdown(!RUNNING)
- isTerminating 是否正在停止(状态在 RUNNING 和 TERMINATED 之间)
- isTerminated 是否已经停止(TERMINATED)
二、源码解析
2.1 状态
ThreadPoolExecutor使用一个AtomicInteger的前3位,表示线程池的状态,后29位表示线程数。
// 接收新任务,并且处理任务队列中的任务,初始状态
private static final int RUNNING = -1 << COUNT_BITS;
//不接收新任务,但是处理任务队列的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
//不接收新任务,不处理任务队列,同时中断所有进行中的工作线程
private static final int STOP = 1 << COUNT_BITS;
//所有任务已经被终止,工作线程数量为 0,到达该状态会执行terminated()
private static final int TIDYING = 2 << COUNT_BITS;
//terminated()已经完成
private static final int TERMINATED = 3 << COUNT_BITS;
此处常量的大小是有顺序关系的,只能从前转移到后面,这样便于状态的比较,软件设计中经常用到。
状态转换
线程池状态之间的转换
- RUNNING -> SHUTDOWN:shutdown()被调用
- (RUNNING or SHUTDOWN) -> STOP:shutdownNow()被调用
- SHUTDOWN -> TIDYING:队列和池均为空
- STOP -> TIDYING:池为空
- TIDYING -> TERMINATED:钩子方法terminated()已经完成。
🤔getPoolSize 为什么是获取HashSet workers 的长度,而不是state的后29位
因为state的后29位并不一定是真实的线程池的数量。后面会看到,在创建新线程前,会使用CAS的方式,先增加线程计数,把创建线程的资格占用住,然后再创建线程。甚至使用ThreadFactory创建线程还可能失败,后续还要减去state中的计数。
🤔所以,为什么非要用一个AtomicInteger来表示,而不是两个?
因为线程的状态的修改与线程数量的修改,是有冲突的,比如线程池状态从RUNNING 变为 SHUTDOWN,这时候就不应该增加Worker了,如果增加线程与状态修改并发了,一个原子操作,就只有一个能成功,不需要使用锁。
2.2 execute
if (command == null)
throw new NullPointerException();
//1. 如果当前Worker数小于corePoolSize,调用addWorker(cmmond,true)新建核心线程处理该任务,addWorker也会检查状态和当前线程数量,如果添加失败(状态不对或线程数>=corePoolSize),返回false,添加成功,直接返回
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.当前Worker数大于等于corePoolSize,线程池还在运行,把任务添加到队列中。
if (isRunning(c) && workQueue.offer(command)) {
//2.1 即使添加成功,也要重新检查,毕竟是并发的,可能就这个间隙,线程池的状态就变了。
//2.1.1 线程池是否关闭
//2.1.2 是否需要新增一个线程
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3.无法入队列,添加非核心线程处理任务,添加失败,则拒绝任务。
else if (!addWorker(command, false))
reject(command);
🤔这段代码是什么意思
else if (workerCountOf(recheck) == 0) addWorker(null, false);
这段代码的含义是,如果当前队列的Worker数量为0,则增加一个没有任务的Worker。很好理解,这段代码之前的代码,向队列中添加任务成功了,却没有Worker去执行,当然需要补一个Worker进去。
🤔可是问题是,什么情况下,会出现这种场景呢?
应该是一个及其特殊的场景。
- 任务提交,正在进入阻塞队列, workQueue.offer(command)
- 所有的Worker满足了退出条件,正在退出
也就是任务刚进入队列,所有的worker都撤了。
- 要设置corePoolSize=0或者设置allowCoreThreadTimeOut为true。worker空闲了足够时间;
- 当前状态不是RUNNING,并且remove(command)失败,也就是对应上一句的IF。不过讲道理,如果remove失败了,应该是被Worker给take走了,worker数量也不会是空,除非阻塞队列有问题。
- 或者上一步addWorker时创建线程直接失败了,比如线程工厂抛出异常。
2.3 addWorker
2.3.1 概述
execute在多处调用了addWorker,用于添加Worker中。
方法签名
/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked. If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
*
* 添加一个以firstTask为第一个任务的Worker到线程池。core标识添加的是否是核心线程。添加失败返回false。
*/
private boolean addWorker(Runnable firstTask, boolean core)
🤔什么场景会添加失败
- 线程池状态不是Running
- 线程数量到达上限,参数core为true时,上限是corePoolSize,false时,上限是maximumPoolSize。
- ThreadFactory创建线程失败
- 线程启动异常(比如start的时候抛出OutOfMemoryError)
2.3.2 源代码
// 1.尝试增加线程计数,注意第一段代码,仅仅是CAS增加计数,相当于在获取一个“资格”,如果超出线程数量的边界(core ? corePoolSize : maximumPoolSize),则直接退出,增加失败
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 1.1 状态不对,直接返回false。除非状态处于SHUTDOWN且队列中有任务且firstTask==null(对应前文addWorker(null, false))。
// 总结一下:线程池没有运行,直接返回false,除非处于SHUTDOWN状态且队列里还有任务待执行。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
// 1.2 自旋+CAS的方式,增加workerCount,数量超过上限则直接返回false。
for (;;) {
int wc = workerCountOf(c);
// CAPACITY是2的29次方减1,线程池状态使用int的后29位存储线程数量,也就是线程数量理论最大值
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// cas成功,跳出retry循环
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 因为状态改变导致CAS失败,重新进入外层循环,重新进行状态判断
if (runStateOf(c) != rs)
continue retry;
// 否则是因为修改线程数量失败,重新尝试内层循环即可。
}
}
// 2. 增加一个Worker,加锁后放入workers(非线程安全的HashSet)
// + 成功则启动线程
// + 失败,调用addWorkerFailed,回退状态。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//这里创建了线程
final Thread t = w.thread;
if (t != null) {
// 锁,需要同步处理workers(非线程安全的HashSet)
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());
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();
//2.1 添加成功,调用Thraed.start
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 2.2 添加失败,回退状态
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
🤔使用ReentrantLock是为了保护什么资源
为了保护记录Worker的hashset——workers,HashSet是非线程安全的,所有关于HashSet的操作——增加Worker/删除Worker/获取size/遍历workers——都需要加锁。
🤔为什么使用存放线程的workers使用非线程安全的HashSet?
狗哥的注释
/**
* Lock held on access to workers set and related bookkeeping.
* While we could use a concurrent set of some sort, it turns out
* to be generally preferable to use a lock. Among the reasons is
* that this serializes interruptIdleWorkers, which avoids
* unnecessary interrupt storms, especially during shutdown.
* Otherwise exiting threads would concurrently interrupt those
* that have not yet interrupted. It also simplifies some of the
* associated statistics bookkeeping of largestPoolSize etc. We
* also hold mainLock on shutdown and shutdownNow, for the sake of
* ensuring workers set is stable while separately checking
* permission to interrupt and actually interrupting.
*/
大概的意思是,主要是为了防止在interruptIdleWorkers的时候,造成中断风暴,如果多个线程同时调用shutdown,会调用interruptIdleWorkers给所有的线程发送中断,如果是线程安全的set,则会同时进行,造成中断风暴。而使用锁,则是串行的,后面执行的interruptIdleWorkers时,会判断是否已经中断,已经中断的,不会再次中断。
🤔addWorkerFailed都做了什么
其实,就是相当于回滚,删除了workers中的Worker,并减去state中的线程数。并顺便调了一下tryTerminate。至于tryTerminate,后面再说。
2.4 Worker
2.4.1 概述
ThreadPoolExecutor需要对线程进行一下封装,以实现:
- 任务结束之后,线程不会退出,而是等待任务队列中的下一个任务;
- 任务出现异常后,线程即使退出,也会启动一个新的线程替代他;
- 空闲的线程,在一定条件下(超时或shutdown),可以退出;
ThreadPoolExecutor用一个内部类Worker实现了Runnable接口,同时,内部封装了一个Thread。另外Worker还继承了AbstractQueuedSynchronizer,实现了锁的功能。
🤔Worker继承了AbstractQueuedSynchronizer,有什么用
李狗哥的解释
/**
* Class Worker mainly maintains interrupt control state for
* threads running tasks, along with other minor bookkeeping.
* This class opportunistically extends AbstractQueuedSynchronizer
* to simplify acquiring and releasing a lock surrounding each
* task execution. This protects against interrupts that are
* intended to wake up a worker thread waiting for a task from
* instead interrupting a task being run. We implement a simple
* non-reentrant mutual exclusion lock rather than use
* ReentrantLock because we do not want worker tasks to be able to
* reacquire the lock when they invoke pool control methods like
* setCorePoolSize. Additionally, to suppress interrupts until
* the thread actually starts running tasks, we initialize lock
* state to a negative value, and clear it upon start (in
* runWorker).
*/
Worker简单的实现了AQS,并在任务的开始和结束,获取、释放锁。而在interruptIdleWorkers(中断空闲线程的方法)方法中,在中断线程前,会尝试获取锁,获取锁成功了,才会中断线程,也就保证了interruptIdleWorkers,不会给正在执行任务的Worker发中断。 interruptIdleWorkers,目的是中断空闲线程,也就是不能中断正在执行的线程。而这个锁,就是这个目的,防止interruptIdleWorkers中断正在执行任务的线程。
interruptIdleWorkers中断相关的代码
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
interruptIdleWorkers会在停止线程和改变线程池的相关方法中调用
- setCorePoolSize()
- setMaximumPoolSize()
- setKeppAliveTime()
- allowCoreThreadTimeOut()
- shutdown()
- tryTerminate()
值得注意的是,shutdownNow并不会调用interruptIdleWorkers,而是interruptWorkers,而这个方法,是不管任务是否正在执行,都会发送中断。
🤔Worker为什么没有设计成可重入的,或者说,为什么没有直接使用ReentrantLock,而是继承AQS
在上面狗哥的注释已经说了,是不想Worker运行的任务,获取到了ThreadPoolExecutor的引用,调用ThreadPoolExecutor的相关方法(比如setCorePoolSize)获取到锁,而中断自己。更具体的例子就是,一个任务**(注意是任务本身)**,在运行的过程中,调用了运行自己的ThreadPoolExecutor.setCorePoolSize方法,而setCorePoolSize在一定条件下,会调用interruptIdleWorkers,如果可以重入,interruptIdleWorkers方法就会获取到锁,进而给这个线程发了中断。 setCorePoolSize中一小段调用interruptIdleWorkers方法的代码块:
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
🤔Worker初始化的时候,为什么设置了AQS的状态为-1
Worker的构造方法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker 直到运行,才能中断
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
Worker在初始化的时候,会设置AQS的state的为-1,直到任务开始运行的时候,在runWorker的开始,才会unlock释放锁;这样就保证从创建到未运行的过程中,不能被中断。
🤔Worker初始化的时候,初始化状态为-1,在runWorker中调用unlock,调用release(1),能解锁成功么?
能,release(1)在AQS中,最后会调用tryRelease,而Worker中实现的TryRelease是这样的,一定能释放成功。
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
2.4.2 Worker源代码
Worker实现了Runnable接口,ThreadPoolExecutor在addWorker时,会把Worker的Thread给start起来,线程中跑的就是Worker的run方法。Worker的run方法,调用的就是ThreadPoolExecutor的runWorker方法,看看李狗哥的注释:
/**
* Main worker run loop. Repeatedly gets tasks from queue and
* executes them, while coping with a number of issues:
*
* 1. We may start out with an initial task, in which case we
* don't need to get the first one. Otherwise, as long as pool is
* running, we get tasks from getTask. If it returns null then the
* worker exits due to changed pool state or configuration
* parameters. Other exits result from exception throws in
* external code, in which case completedAbruptly holds, which
* usually leads processWorkerExit to replace this thread.
*
* 2. Before running any task, the lock is acquired to prevent
* other pool interrupts while the task is executing, and then we
* ensure that unless pool is stopping, this thread does not have
* its interrupt set.
*
* 3. Each task run is preceded by a call to beforeExecute, which
* might throw an exception, in which case we cause thread to die
* (breaking loop with completedAbruptly true) without processing
* the task.
*
* 4. Assuming beforeExecute completes normally, we run the task,
* gathering any of its thrown exceptions to send to afterExecute.
* We separately handle RuntimeException, Error (both of which the
* specs guarantee that we trap) and arbitrary Throwables.
* Because we cannot rethrow Throwables within Runnable.run, we
* wrap them within Errors on the way out (to the thread's
* UncaughtExceptionHandler). Any thrown exception also
* conservatively causes thread to die.
*
* 5. After task.run completes, we call afterExecute, which may
* also throw an exception, which will also cause thread to
* die. According to JLS Sec 14.20, this exception is the one that
* will be in effect even if task.run throws.
*/
runWorker方法,主要就是一个循环,不断的从任务对列取任务并执行。要处理下面这些问题:
- 起动的时候,worker内可能有一个初始任务,这样就不用从任务队列中取任务了,其他时候,都会从任务队列中获取任务。如果取任务的时候,返回了null(可能因为ThreadPoolExecutor状态改或配置改变),线程退出。也有可能因为任务抛出异常,这时会调用processWorkerExit,重新创建一个线程替代这个退出的线程。
- 跑任何任务之前,获取锁,防止任务执行的时候被中断,这个之前说过。
- 执行任务之前,会调用beforeExecute,留给子类复写的方法,可能会抛出异常
- beforeExecute正常执行完成后,执行任务,把RuntimeException,Error和Throwables都捕获,然后重新抛出去,捕获就是为了在finally中afterExecute去处理而已,afterExecute也是给子类来实现的。抛出的异常,由Thread的UncaughtExceptionHandler来处理。这里抛出异常,会导致当前线程退出,当然,如果ThreadPoolExecutor状态不是stop,还会再补一个新的Worker。因为没有办法抛出Throwables,所以使用Error包装了一下,再抛出。
- 执行afterExecute。
2.4.2.1 runWorker
Worker的run方法,就是调用的ThreadPoolExecutor的runWorker方法:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts,这个是解锁Worker初始化时的锁,如果Worker在初始化时,不加锁,而接收到了interruptIdleWorkers的中断,在下面getTask会触发该中断
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();//上锁,防止运行时被interruptIdleWorkers中断
// 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之前,清空中断,之后,一定有中断,注意这里的recheck。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);//before,留给子类复写
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);//Throwable不能抛,所以封装一下
} finally {
afterExecute(task, thrown);//after,也是留给子类复写
}
} finally {
task = null;
w.completedTasks++;//计数
w.unlock();
}
}
//Abruptly:突然的,如果是因为异常退出,不会执行到这里,completedAbruptly=true;
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
runWorker是一个循环,循环的条件是:
task != null || (task = getTask()) != null)
每次循环结束的finally中,都会把task设置为null,task!=null只有在初次运行,且firstTask!=null时才成立,其余都要运行getTask方法,也就是从任务队列中取任务。当getTask返回null的时候,Worker就会退出,也就是说,Worker在什么时候退出,是在getTask中控制的。
2.4.2.2 getTask
狗哥的注释:
/**
* Performs blocking or timed wait for a task, depending on
* current configuration settings, or returns null if this worker
* must exit because of any of:
* 1. There are more than maximumPoolSize workers (due to
* a call to setMaximumPoolSize).
* 2. The pool is stopped.
* 3. The pool is shutdown and the queue is empty.
* 4. This worker timed out waiting for a task, and timed-out
* workers are subject to termination (that is,
* {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
* both before and after the timed wait, and if the queue is
* non-empty, this worker is not the last thread in the pool.
*
* @return task, or null if the worker must exit, in which case
* workerCount is decremented
*/
理解: getTask以阻塞的方式从任务队列中获取任务,是否设置超时时间,根据ThreadPoolExecutor的配置和当前的Worker数量决定。getTask返回null,有以下几种场景:
- setMaximumPoolSize方法被调用,并且当前Woker数量大于新的maximumPoolSize;
- ThreadPoolExecutor停止了
- ThreadPoolExecutor被shutdown且任务队列是空的
- 获取任务的时候等待超时,并且当前Worker可以退出(具体要看当前Woker数量和CorePoolSize/allowCoreThreadTimeOut的配置)
源代码走起:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary. 停止了或者被shutdown并任务队列是空的,返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// wc > maximumPoolSize或者超时且满足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是做什么的,看代码就非常容易了。还要说的一点是,之前一直提到的interruptIdleWorkers,就是interrupt这里,从runWorker中可以看到,这里是不会加锁的,并且,从任务队列中阻塞的获取任务,是可以响应中断的,会抛出InterruptedException异常,然后getTask会执行timedOut = false;这行代码,标识不是超时。在新的一次循环中,在这段代码中,就可能会返回null,达到worker退出的目的:
// Check if queue empty only if necessary. 停止了或者被shutdown并任务队列是空的,返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
2.4.2.3 processWorkerExit
runWorker跳出循环后(可能是正常退出,也可能是抛出异常后退出,由completedAbruptly参数标识),会执行processWorkerExit方法。这个方法会把这个worker从workers这个hashSet中移除。在一定条件下(比如异常退出),还会再补一个线程。
源代码:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//getTask在返回null前,就会调用decrementWorkerCount方法,而异常退出则不会,所以这里要补一下
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
// 从workers中删除
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试退出,后面会说这个方法
tryTerminate();
// 异常退出或者没有worker了且任务队列不为空,会补一个worker
int c = ctl.get();
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);
}
}
2.5 线程池关闭 shutDown & shutDownNow
关闭线程池,可以使用shutDown和shutDownNow
- shutDown 不接受新的任务,但之前提交的任务都会执行完毕。会给所有空闲的线程发送中断,以让他们退出。
- shutDownNow 不接受新的任务,并且,还在任务队列中的任务也会被移除(并且作为返回值返回),并且给所有Worker发送中断。
看下shutDown的代码:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//这里要遍历workers设置权限,要加锁
advanceRunState(SHUTDOWN);//改状态
interruptIdleWorkers();//这里之前说过,为了防止不必要的interrupt,使用ReentrantLock锁住。
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
代码没几行,先设置权限,改状态为SHUTDOWN,给空闲线程发中断,调个子类方法,最后tryTerminate。除了最后的tryTerminate,其余都很好理解。
同样,shutDownNow也非常简单,不过是interruptIdleWorkers改为interruptWorkers,强行给所有worker发中断。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
tryTerminate
tryTerminate,从名字就可以看出来,是尝试终止,这要从ThreadPoolExecutor如何终止说起。其实无论shutdown还是shutDownNow,都无法立即终止ThreadPoolExecutor。即使shutDownNow,如果有正在运行的任务,也不过是给他们发中断,任务不一定会立即停止(甚至都不会响应中断)。想要确切的得知ThreadPoolExecutor何时停止,只能等,也就是调用awaitTermination方法,去等着ThreadPoolExecutor结束。
🤔那到底什么时候停止呢?
没有什么太好的办法,只能在所有可能会停止的地方,加上tryTerminate,判断ThreadPoolExecutor是否停止了,停止了,就更改状态,并通知所有awaitTermination等待的线程。
调用了tryTerminate的方法:
- ThreadPoolExecutor.addWorkerFailed(Worker)
- ScheduledThreadPoolExecutor.onShutdown()
- ThreadPoolExecutor.processWorkerExit(Worker, boolean)
- ThreadPoolExecutor.purge()
- ThreadPoolExecutor.remove(Runnable)
- ThreadPoolExecutor.shutdown()
- ThreadPoolExecutor.shutdownNow()
目的
tryTerminate方法的目的就是,在一定条件下(SHUTDOWN并且任务队列&&workers都是空 || STOP并且workers都是空),把线程池的状态改为TERMINATED,并通知awaitTermination等待的线程。当状态>=SHUTDOWN,且没有任务的时候,终止空闲线程。
🤔问题来了,什么场景下,状态>=SHUTDOWN,没有任务了,却还有空闲线程!!!
在shutDown时,正在执行任务的worker,是不会被中断的,当多个worker执行任务完成后,调用getTask方法,走到了下面这几行代码:
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
假设有10个worker都在这里并发了,而任务队列中还有5个任务。那么这里都能通过,但是在真正获取任务的时候,只有5个能够获取到,继续执行任务,另外五个,没有收到中断,则会一直阻塞到take上,也就是空闲状态。这就是在SHUTDOWN状态下,还有空闲线程的场景。还记得么,每个线程退出的时候,都回调用processWorkerExit方法,而这个方法调用了tryTerminate,会给这些空闲线程发中断,让他们退出。
tryTerminate源码:
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) || //正在运行,直接返回
runStateAtLeast(c, TIDYING) ||状态是TIDYING以后,说明其他线程tryTerminate正在运行,退出
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))//任务队列中还有任务,退出
return;
//这里在中断空闲线程
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
//修改状态,并通知等待线程,话说真的有必要自旋么,还是CAS习惯性写法
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
}
}
awaitTermination
没啥好说的,使用condition.await实现的。在tryTerminate中signalAll的。