最近迁移一个项目,涉及到线程池的问题。特意看了一下ThreadPoolExecutor源码,记录一下。思考几个问题
ThreadPoolExecutor的基本实现思路是什么?
- 在线程池正在运行(running)的状态下,提交任务到线程池以后,如果线程数量小于corePoolSiez,则创建新的工作线程加入workers中,并启动该线程;如果大于等于corePoolSiez,则加入任务队列中,等待工作线程空闲的下来再处理这些任务。如果加入队列也失败,则最后尝试新建工作线程处理。
- 在线程池正在关闭(shutdown)的状态下,拒绝提交的任务到线程池,中断所有空闲的线程。这里空闲的线程指那些等待任务执行的线程,也就是在getTask方法中阻塞或者等待的线程。
ThreadPoolExecutor的数据结构大概是怎么样的?
用int类型来记录ThreadPoolExecutor的内部属性,一个int整型有32位,其中高前3位表示ThreadPoolExecutor的5种运行状态,低29位保存ThreadPoolExecutor的工作线程数量。
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;
5种运行状态如下。
- RUNNING:正常运行状态;
- SHUTDOWN:线程池关闭状态,调用shutdown设置的状态;
- STOP: 线程池停止状态,调用shutdownNow设置的状态
- TIDYING:用来继承ThreadPoolExecutor后提供扩展用的,顾名思义,TIDYING表示整理,也就是线程池终止前进行整理。
- TERMINATED:线程池终止状态。
// 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;
2个重要的结构。workers是工作线程容器,里面的线程就像一个不知疲倦的工人,不停的从任务队列中提取任务执行。workQueue是工作任务队列,当工作线程来不及处理提交的任务时,就把任务放入队列中。
private final HashSet<Worker> workers = new HashSet<Worker>();
private final BlockingQueue<Runnable> workQueue;
几个重要的属性
- corePoolSize:工作线程核心数量
- maximumPoolSize:工作线程最大数量
private volatile int corePoolSize;
private volatile int maximumPoolSize;
任务怎样提交到线程池中?
一般调用execute方法提交任务到线程池中.
addWork方法主要是增加工作线程,首先根据线程池状态和设定容量是否允许增加,如果允许,加入工作线程容器中,然后启动该线程。
接下来我们返回来看addWork失败后会怎么样:先尝试加入任务队列,加入成功后进行double-check。如果加入队列后线程池已经shutdown,这是需要重新拒绝这个任务;
如果线程池中没有工作线程存活,则需要重新增加工作线程。否则直接拒绝任务。
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);
任务怎样执行?
提交的任务是在工作线程中执行的。所谓的工作线程就内部类Work。这里要注意一个概念。工作线程和任务是不一样的。看起来好像都是线程的概念,但却大不相同。工作线程是是执行任务的线程,通过Thread.start启动运行的;而任务是由外部的业务代码组成,就是一个run方法而已。
工作线程是委托runWorker运行,我们跟进去看看。
主要实现是While无限循环,如果是第一次运行则执行firstTask,否则不断从任务队列中获取任务执行。
那什么时候runWorker会跳出循环终止?getTask获取到null时,跳出循环。返回null有两种情况,
1.线程池停止;
2.线程池关闭并且任务队列为空;
3.工作线程超出设定的maximumPoolSize;
4.当(allowCoreThreadTimeOut || workerCount > corePoolSize) == true并且工作线程等待任务超时。
跳出循环后,工作线程会进行退出处理
怎么关闭线程池?
ThreadPoolExecutor提供了shutdown和shutdownNow两种方法关闭线程池。因为java中断无法做到抢断,而是协作式的中断,所以如果你的任务忽略中断,shutdown和shutdownNow效果是一样的。
- shutdown中断空闲的工作线程,等待工作任何队列处理完后再返回;
- shutdownNow直接中断所有工作线程,不等待任务队列处理完,返回任务队列。
从细节看,interruptIdleWorkers每次中断线程的时候,首先会尝试条用tryLock获取锁,如果获取失败,说明这个线程并不空闲,正在处理其他任务,则不中断该线程;
而interruptWorkers最终调用interruptIfStarted方法,不会调用tryLock获取方法,简单暴力的关闭了线程。
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
//Worker中方法
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
shutdown什么时候才处理完任务队列?可以用awaitTermination方法,等待termination信号才退出或者超时退出。
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();
}
}
由此可见,优雅停止线程池方式是
....
executor.shutdown();
executor.awaitTermination(timeout, TimeUnit.SECONDS);
...