Java中几乎所有需要异步或并发执行任务的程序,都需要使用线程池。这有3个好处:
- 降低资源消耗,通过重复利用已有线程,来降低线程创建、销毁造成的消耗;
- 提供响应速度,当任务到达时,不用等待线程创建就能立即执行;
- 提高对线程的管控,避免无限制的创建,实现统一分配、监控和调优。
今天,我们一起来看看Java中线程池的实现。
ThreadPoolExecutor
Java中线程池实现是java.util.concurrent.ThreadPoolExecutor类。顶层接口Executor是@FunctionalInterface的,仅有一个方法void execute(Runnable command)。
线程池处理任务的流程大体如下:
- 起初线程池中没有线程,当一个任务提交给线程池后,会创建一个新线程来执行任务;
- 当运行中线程数到达corePoolSize时,继续的提交任务会被加入workQueue队列排队;
- 如果队列有界且已经放满时,将创建新线程来救急;
- 如果线程数到达maximumPoolSize的,却仍有新任务,这时会执行拒绝策略;
- 超过corePoolSize的救急线程,如果在一定时间没有获得任务(即空闲),将会被回收。空闲时间由keepAliveTime 和 unit 来控制。
1 线程池状态表示
ThreadPoolExecutor中,使用一个AtomicInteger属性(称为ctl))的高3位来表示线程池状态(即runState),低29位表示线程数量(即workerCount),最大线程数为(2^29)-1 (约500万多)。使用CAS原子操作更新ctl。
线程池初始状态为RUNNING,workerCount=0。
注意,RUNNING时最高位为1,即负值。因此从数字上来比较:TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
| 状态名 | 高3位 | 接收新任务 | 处理队列任务 |
|---|---|---|---|
| RUNNING | 111 | Y | Y |
| SHUTDOWN | 000 | N | Y |
| STOP | 001 | N | N |
| TIDYING | 010 | N | N |
| TERMINATED | 011 | N | N |
2 构造方法
ThreadPoolExecutor类主要属性如下:
// 任务队列
private final BlockingQueue<Runnable> workQueue;
// 主要用于操作workers时加锁
private final ReentrantLock mainLock = new ReentrantLock();
// worker集合,非线程安全的,因此需要mainLock
private final HashSet<Worker> workers = new HashSet<Worker>();
// 线程池曾创建线程的最大数
private int largestPoolSize;
// 完成任务的计数器。仅在工作线程终止时更新
private long completedTaskCount;
// 线程工厂
private volatile ThreadFactory threadFactory;
// 任务拒绝策略
private volatile RejectedExecutionHandler handler;
// 空闲线程最大存活时间,在线程数量超过corePoolSize或allowCoreThreadTimeOut=true时起作用
private volatile long keepAliveTime;
// 核心线程是否空闲超时,默认为false
private volatile boolean allowCoreThreadTimeOut;
// 核心线程数
private volatile int corePoolSize;
// 最大线程数
private volatile int maximumPoolSize;
全参构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
3 ThreadFactory
一个按需创建新线程的工厂类,消除了对new Thread()调用的硬依赖。
public interface ThreadFactory {
// 可以为线程设置更多属性,如priority, name, daemon, ThreadGroup等.
Thread newThread(Runnable r);
}
Executors类中提供了一个实现DefaultThreadFactory:创建普通优先级、非守护线程。
创建ThreadPoolExecutor时如果不提供threadFactory,那么默认使用
DefaultThreadFactory。
4 拒绝策略
一个处理器,用来处理ThreadPoolExecutor不能执行的任务。
public interface RejectedExecutionHandler {
// 当一个任务被提交时,却没有更多可用线程或任务队列已满,将执行该方法。
// 可能会抛出RejectedExecutionException
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
ThreadPoolExecutor中提供了4 种拒绝策略实现,我们也可自行扩展:
- AbortPolicy:让调用者抛出 RejectedExecutionException 异常,这是默认策略;
- CallerRunsPolicy:让调用者运行任务
- DiscardPolicy:丢弃本次任务
- DiscardOldestPolicy:放弃队列中最早的任务,提交该任务。
来看一下DiscardOldestPolicy实现:丢弃队列头任务,提交当前任务。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
// 忽略返回值,即丢弃队列头任务
e.getQueue().poll();
// 提交当前任务
e.execute(r);
}
}
}
5 工作流程
ThreadPoolExecutor.execute()方法主要流程如图所示。
在ThreadPoolExecutor.execute()方法中,反复对ctl进行判断,因为可以并行提交任务,且线程池状态也随时可能发生变化。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 线程数小于corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 创建核心线程,可能会失败
if (addWorker(command, true))
return;
c = ctl.get();
}
// 线程数大于或等于corePoolSize时,尝试将任务放入队列;
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 线程池非Running状态时,从队列移除该任务
if (!isRunning(recheck) && remove(command))
// 执行拒绝策略
reject(command);
// 线程池Running状态,但任务未放入队列中时
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 将任务放入队列失败,创建救急线程
else if (!addWorker(command, false))
reject(command);
}
6 工作线程Worker
线程池创建线程时,会封装成工作线程Worker,Worker执行完firstTask后,会循环获取任务队列中任务来执行。
// Worker类部分代码
// 实现了AQS,自身就可以lock/unlock,不借助于ReentrantLock。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
//关联的线程
final Thread thread;
// 创建work时的首个任务(不是从队列中获取的)
Runnable firstTask;
// 以完成任务数
volatile long completedTasks;
Worker(Runnable firstTask) {
// 防止创建后、执行runWorker前,被lock住。
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
// 循环获取任务执行
public void run() {
runWorker(this);
}
}
再来看ThreadPoolExecutor.runWorker()方法逻辑:当执行完firstTask后,继续从队列getTask()来执行;如果未获取到任务,则退出且销毁线程。
final void runWorker(Worker w) {
// 提交任务的线程
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
// 记录worker是否非正常退出
boolean completedAbruptly = true;
try {
// firstTask或从队列拉取任务
while (task != null || (task = getTask()) != null) {
w.lock();
try {
// 默认空实现,可扩展
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 执行提交的任务
task.run();
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
// 默认空实现,可扩展
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// worker退出
processWorkerExit(w, completedAbruptly);
}
}
6.1 Worker何时被启动的?
在ThreadPoolExecutor.execute()中多出调用了addWorker(),后者在worker创建成功后就立即启动了它。
- Worker实现了Runnable接口;
- 创建Worker.thread时,用new Thread(worker);
- thread启动后,线程任务就是Worker.run();
- Worker.run()仅仅是调用ThreadPoolExecutor.runWorker()
6.2 何时会从队列中获取不到任务?
在ThreadPoolExecutor#getTask方法中,使用for (;;)死循环,直到从队列中获取一个任务或者返回null时才return。以下情况时将获取不到任务:
- 线程池状态不满足时返回null:非running状态,或者非running、SHUTDOWN状态且队列为空时
- 触发线程数限制或空闲超时时返回null:
6.3 非核心线程的空闲超时如何实现?
- 当有空闲超时限制时,在从队列获取任务时,使用阻塞带超时的
poll(long timeout, TimeUnit unit)方法,超时后将返回null,进而worker退出; - 而不受超时限制时,使用阻塞无超时的
take()方法,直到从队列获取到一个任务时才继续执行。
// 限制空闲线程数否
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
6.4 核心线程与非核心线程有无标识?
结论:线程池中某个线程被创建后,并非固定就是核心的或非核心的。
- 首先,
Worker类中没有一个属性来标识线程核心与否。 - 其次,
addWorker()方法中,仅根据core来区分当前线程数与corePoolSize还是maximumPoolSize来比较。
private boolean addWorker(Runnable firstTask, boolean core) {
for (; ; ) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) // core仅用于此处判断
return false;
// ......
}
- 最后,在
getTask()方法中,只要当前线程数大于corePoolSize,当前线程都有可能被淘汰。
6.5 任务执行异常时补充新worker
在processWorkerExit()方法中,将从ThreadPoolExecutor.workers中移除当前worker,线程也被销毁。对于completedAbruptly参数:
- 由于没有获取到可执行任务时,completedAbruptly = false,即正常退出。此时,如果当前线程数不大于核心线程数,且任务队列不为空时,将补充一个新worker;否则继续worker退出。
- 当因为执行任务发生异常时,completedAbruptly=true,将补充一个新worker。
由此可知,线程池中线程因执行任务异常而意外退出时,将有机会补充一个新线程。
6.6 预创建核心线程
起初线程池中无任何线程,当新任务到达且线程数小于corePoolSize时,才去创建并启动一个核心线程,来执行该任务。这在响应时间上有损耗。
ThreadPoolExecutor提供了两个方法,可以在创建线程池后,立即启动核心线程,即使没有任务达到——即预热
- prestartCoreThread():启动一个核心线程
- prestartAllCoreThreads():启动所有核心线程