1.线程池状态
2.线程池状态流转图
3.线程池工作流程图
4.主要参数
//任务队列
private final BlockingQueue<Runnable> workQueue;
//线程池锁
private final ReentrantLock mainLock = new ReentrantLock();
//工作线程队列 使用HashSet存储
private final HashSet<Worker> workers = new HashSet<Worker>();
//条件锁
private final Condition termination = mainLock.newCondition();
//记录线程池达到的最大线程数
private int largestPoolSize;
//完成任务数统计
private long completedTaskCount;
//线程工程
private volatile ThreadFactory threadFactory;
//饱和策略,默认直接拒绝
private volatile RejectedExecutionHandler handler;
//线程存活时间,即线程等待任务阻塞最大时间(getTask()方法中应用)
private volatile long keepAliveTime;
//允许核心线程过期,默认false,如果true,则共用keepAliveTime
//使用allowCoreThreadTimeOut(boolean value)可打开
private volatile boolean allowCoreThreadTimeOut;
//核心线程数
private volatile int corePoolSize;
//允许的最大线程数
private volatile int maximumPoolSize;
//默认拒绝策略
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
5.主要方法介绍
5.1.execute方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
1.如果运行的线程小于corePoolSize,则尝试使用给定的命令作为第一个
任务来启动新线程。对addWorker的调用会自动地检查runState和
workerCount,从而通过返回false来防止在不应该添加线程的情况下添加
从而导致错误。
2.如果一个任务可以成功地排队,那么我们仍然需要再次检查是否应该添加
一个线程(因为现有的线程在最后一次检查后死亡),或者池在进入这个方法
后关闭。所以我们重新检查状态,如果需要,如果停止,则回滚队列;如果没
有,则启动新线程。
3.如果无法对任务排队,则尝试添加新线程。如果它失败了,我们知道我们
被关闭或饱和,因此拒绝任务。
*/
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);
}
5.2.Work对象
其继承了AQS,利用state状态来判断线程是否空闲(线程执行任务时会先通过lock来获取锁)
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
//调用这个工人worker的线程
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
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); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** 将任务监听循环委托给外部运行工人,核心方法 */
public void run() {
//
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
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(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
5.3.runWorker(this)
下面介绍runWorker(this)这个核心方法
/**
主流程循环。重复从队列获取任务并执行它们,同时处理一些问题:
1.我们可能从一个初始任务开始,在这种情况下,我们不需要得到第一个。否则,只
要池在运行,我们就从getTask获取任务。如果返回null,则工作进程将由于更改池状态
或配置参数而退出。外部代码中的异常抛出导致跳出循环,在这种情况下
completed参数保持为true,这通常会导致processWorkerExit替换这个线程(所以要尽
量在Runnable业务代码中try catch异常并处理,否则会导致线程池销毁并重新创建线
程)
2.在运行任何任务之前,锁需要被获取,以防止任务执行过程中中断,然后我们确保除
非线程池停止,否则这个线程不会有任何中断。
3.每个任务运行之前都有一个对beforeExecute的调用,这可能会抛出一个异常,在这
种情况下,会导致线程死亡(completed true 循环中断),任务也不会执行。
4.假设beforeExecute正常完成,我们运行这个任务,收集它抛出的任何异常并发送给
afterExecute。我们分别处理RuntimeException、错误(规范中保证我们会捕捉到这两个
错误)和任意的可抛出对象。因为我们不能在Runnable.run中重新抛出Throwables,所
以我们将它们封装在错误中(到线程的错误中) UncaughtExceptionHandler)。抛出的异
常都会导致线程死亡。
5.run完成后,我们调用afterExecute,它也可能抛出一个异常,这也会导致线程死亡。
根据JLS 14.20,这个异常即使在task.run抛出时也有效。
异常机制的最终效果是,afterExecute和线程的UncaughtExceptionHandler拥有关于用
户代码遇到的任何问题的尽可能准确的信息。
*/
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
/**
这个参数决定了线程是否会被销毁并重建,如果正常结束则会为false(按照正常流
程执行,判断线程数是否大于核心线程,小于则创建线程,否则就结束),如果因
异常而结束则会保持为true,这时会将改线程所属的worker 从workerQueue中
remove,然后重新添加一个新的worker到队列里,然后该线程执行结束,销毁,也
就是会重新更换worker
*/
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.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++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//结束后处理当前worker,由调度当前worker的线程执行
processWorkerExit(w, completedAbruptly);
}
}
5.4.getTask()
先看下其中一个很重要的方法 getTask() ,循环结束的判断条件是怎样的呢?
/**
**执行阻塞或超时等待任务**,取决于当前的配置,或者当这个线程因以下的原因必须退出时返回null:
1.有多个maximumPoolSize工作者(由于调用setMaximumPoolSize)
2.线程池被停掉了
3.线程池关闭,任务队列为空
4.当worker等待一个任务超时,超时的worker在超时等待前后都有可能被终止,
(即当 allowCoreThreadTimeOut || workerCount > corePoolSize)
如果队列不是空的,那么这个worker不是池中的最后一个线程。
*/
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.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//keepAliveTime生效的地方,如果是需要超时回收的线程则执行超时阻塞,否则就执行阻塞,直到获取到任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
//阻塞时如发现中断,会抛InterruptedException异常,catch处理中断
timedOut = false;
}
}
}
5.5.processWorkerExit(w, completedAbruptly)
继续看 processWorkerExit(w, completedAbruptly)这个方法做了些什么?
该方法主要对worker进行清理,从hashSet中移除当前的worker,并尝试终止线程池,若线程池未终止,则去判断线程是否需要更换
只有在业务逻辑异常导致线程未正常结束,或者任务正常完成但线程数<允许的最小线程数时,才会新增worker。
(核心线程数在不允许过期时,线程池运行状态下是不会进入这个方法的(要么执行任务,要么会阻塞在getTask()方法里),除非在遇到中断(线程池关闭或其它中断场景)或者业务异常时,才会进入该方法进行核心线程资源的回收和处理)
addWorker(null, false)方法在下面的条件下 不会再新增worker
/**
此方法从worker队列中移除线程。
worker清理。仅从worker线程(runWorker方法)调用。除非completed参数被
修改为false,否则假定workerCount已经被调整则考虑退出。
如果线程因用户任务异常而退出,或者运行的工作线程小于corePoolSize,
或者队列非空但没有工作线程,则可能终止线程池或替换worker及线程。
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
/**
尝试终止线程池
将线程池状态转换到终止状态,如果以下条件满足:
(线程池当前状态为SHUTDOWN&&池和任务队列为空)或(状态为STOP&&池为空)。
如果满足条件,但worker数量>0,则中断一个空闲工人,以确保关闭信号传播。
必须在任何可能导致线程池终止的操作之后调用此方法,这样做的主要目的是:
1.减少空闲worker数量
2.在关闭期间从队列中删除任务。
3.线程池的终结
*/
tryTerminate();
int c = ctl.get();
//如果线程池还没有终止(状态<STOP),则执行下面的逻辑
if (runStateLessThan(c, STOP)) {
//如果worker是正常的任务执行完成,则去执行判断线程数逻辑,根据当前线程数和允许的min值比较,>=min则表示线程无需重建
//最大线程数类型的线程会在等待存活时间超时后,workers.remove(w);线程执行完毕,回收work,不再新增线程。
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//当线程数不足,或当前worker是因为异常而跳出任务监听的循环,则重建一个新线程(替换销毁的线程)
addWorker(null, false);
}
}
5.6.tryTerminate()
tryTerminate()又干了什么呢?
一个关键的步骤在 interruptIdleWorkers(ONLY_ONE);
这个方法下面会介绍到,先说下在这里使用该方法有什么作用?
中断一个空闲线程(注意是一个),以使得中断信号可以传播
那么问题来了,为什么只中断一个线程,这种中断信号的传播又是怎么做到的呢?
它的传播机制:
该中断会在getTask()方法阻塞的地方得到响应,这时线程会跳出runTask()循环,进入processWorkerExit方法,继续调用tryTerminate()中断另外一个空闲线程(达到传播的效果,这种机制保证了线程池在shuntdown()时的正常关闭)
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
//关键处理步骤,中断一个空闲线程,以使得中断信号可以传播
//该中断会在getTask()方法阻塞的地方响应得到响应,线程在runtask()方法里跳出循环后,会进入processWorkerExit方法,
//继续调用tryTerminate()传播中断(保证线程池的正常关闭)
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));
//唤醒调用awaitTermination阻塞方法的线程,线程池终止完成
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
说到 tryTerminate(); 那就要了解线程池的关闭,看下线程池是如何关闭的,主要涉及以下几个方法
shutdown()
shutdownNow()
awaitTermination(long timeout, TimeUnit unit)
finalize()
5.7.shutdown()
首先来看shutdown()方法做了什么
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查关闭进入许可
checkShutdownAccess();
//将线程池状态改为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断空闲工人线程
interruptIdleWorkers();
// hook for ScheduledThreadPoolExecutor
onShutdown();
} finally {
mainLock.unlock();
}
//尝试线程终止结束
tryTerminate();
}
5.8. interruptIdleWorkers()
其中主要关闭方法为 interruptIdleWorkers();
负责中断空闲线程,通过tryLock判断线程是否空闲(线程运行或更改会加锁)
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();
}
}
5.9.shutdownNow()
下面来看shutdownNow()
可以看到区别在于 interruptWorkers();这个方法,改方法中断了所有线程,包括在执行任务的线程,且最终将未完成队列返回
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//线程池状态改为STOP
advanceRunState(STOP);
//中断所有线程
interruptWorkers();
//取任务队列未完成任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
//返回未完成任务队列
return tasks;
}
shutdown() shutdownNow() 都是采用中断方式结束关闭线程池,执行关闭方法后,线程池在添加任务时,若池状态为SHUTODOWN或STOP会直接拒绝任务,抛RejectException异常
5.10.awaitTermination(long timeout, TimeUnit unit)
那**awaitTermination(long timeout, TimeUnit unit)**是做什么呢
该方法若线程池状态为TERMINATED,则返回true,否则会等待timeout时长,若等待超时,则返回false,表示线程仍未关闭终结
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
5.11.finalize()
如果我们不手动去shutdown(),那谁来保证线程池的关闭调用呢
答案是finalize()方法,该方法在gc垃圾回收时调用(相当于c++的析构函数),具体为在gc第一次标记完成后会进行一次刷选,筛选的条件是此对象是否有必要执行 finalize()方法,如果该对象被判定为有必要执行finalize()方法,会将该对象统一放到F-Queue里,由一个低优先级的线程去挨个调用其finalize()方法,然后进行第二次小范围标记,然后开始执行清除算法,回收资源(当然对象资源也可以在选择在这个方法里去重新被关联到引用链上,从而避免被回收)。详细介绍看这里:jvm浅析
protected void finalize() {
SecurityManager sm = System.getSecurityManager();
if (sm == null || acc == null) {
//由jvm在垃圾回收前调用shutdown尝试关闭
shutdown();
} else {
PrivilegedAction<Void> pa = () -> { shutdown(); return null; };
AccessController.doPrivileged(pa, acc);
}
}
5.12.线程池的优雅关闭
那线程池如何优雅的关闭呢,下面介绍一种方式供参考
//设置关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
executor.shutdown();
//设定最大重试次数
int retry = 2;
try {
//每次等待 10 s,重试
if (!executor.awaitTermination(10, TimeUnit.SECONDS) && retry-- > 0) {
if (retry == 1){
//调用shutdownNow()取消正在执行的任务,SHUTDOWN->STOP是被允许的
executor.shutdownNow();
}else {
log.error("线程池任务未正常执行结束");
}
}
} catch (InterruptedException ie) {
//重新调用 shutdownNow
executor.shutdownNow();
}
}
});
jvm的钩子调用过程如下图
参考:JVM关闭与关闭钩子
ok,先到这里了
当然还有线程池的一些创建方法,因为比较通用,就不做介绍了。