【JUC 并发编程】— 线程池(下)
构造方法
创建线程池的方法如下:
ExecutorService executor = Executors.newFixedThreadPool(8);
但是 《阿里 Java 开发手册》中禁止使用Executors
工具类的方式创建线程池,而是推荐使用ThreadPoolExecutor
的构造方法直接创建。ThreadPoolExecutor
构造方法如下:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
可以看到核心构造方法有 7 个参数,意义如下:
- corePoolSize:线程池核心线程数。
- maximumPoolSize:线程池中允许最大的线程数。
- keepAliveTime:如果线程池中线程数大于 corePoolSize,多余的线程空闲时间,过了该时间,线程将终止。
- unit:keepAliveTime 时间单位,不管定义的时候是什么单位,最后都会转换成纳秒。
- workQueue:任务队列。
- threadFactory:线程工厂,用来创建线程池中线程。
- handler:当队列满了,将采用的拒绝策略拒绝任务。
重要属性
线程池中有几个比较重要的属性,在深入源码前先混个眼熟
ctl
源码中是这么定义的:The main pool control state, ctl, is an atomic integer packing two conceptual fields
,意思是说:线程池的状态变量,AtomicInteger 类型的,主要包装了两个概念上的属性
- workerCount:线程池中线程的数量
- runState:线程池的状态
ctl 定义:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
通过 ctl 可以获取到workerCount
和runState
,如下
// Packing and unpacking ctl
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; }
线程池状态
线程池中定义了下面这几种状态
// 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; // 不接受新任务,也不处理队列中的任务,还要中断进行中(in-progress)的任务
private static final int TIDYING = 2 << COUNT_BITS; // 此时所有的任务终止,线程数为0,这个状态只是调用 terminated() 方法前的过渡状态
private static final int TERMINATED = 3 << COUNT_BITS; // terminated() 方法调用完成,线程池终止
可以看到线程池的这几种状态都是数字类型的,而且这些数字的顺序很重要,线程池状态随着时间推移,只会单调递增,也就说状态只会从 RUNNING 流转到 TERMINATED,而不能反过来。
源码分析
代码中使用线程池如下:
int processorCount = Runtime.getRuntime().availableProcessors();
// 这里请忽略创建方式
ExecutorService executor = Executors.newFixedThreadPool(processorCount);
threadPool.execute(new Runnable() {
@Override
public void run() {
// do sth
}
});
execute()
execute() 方法代码如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
// 获取
int c = ctl.get();
// 如果线程数 < 核心线程数
if (workerCountOf(c) < corePoolSize) {
// 则调用 addWorker() 方法创建线程,并处理任务
if (addWorker(command, true))
return;
c = ctl.get();
}
// 到这里,说明线程数 == 核心线程数,
// 则调用 workQueue.offer(command) 入队
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);
}
// 到这里,说明队列也满了
// 如果 addWorker() 返回 false,说明线程数已经达到了最大线程数(maximumPoolSize),
// 则拒绝这个任务
else if (!addWorker(command, false))
reject(command);
}
注释中说明了execute
方法的主要逻辑:
- 1、如果线程池中线程数小于核心线程数,则直接新建线程来处理这个任务;
- 2、如果线程池中线程数等于核心线程数,则将任务加入队列(这里面有个 double-check,防止线程池状态变动);
- 3、如果加入队列失败(说明队列满了),则新建线程来处理任务;
- 3.1、如果新建线程失败(说明线程池线程数达到了最大线程数),则拒绝这个任务;
- 3.2、新建线程成功,直接处理这个任务;
addWorker()
首先看上面代码中第一个 if 逻辑:如果线程池中线程数小于核心线程数,就调用addWorker()
方法新增线程。addWorker()
代码如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 如果线程池状态为 SHUTDOWN(此状态下,线程池不接受新任务,只处理队列中剩余的任务),
// 并且任务队列为空,说明线程池中已经没任务要处理,那还新建啥线程。。。直接返回 false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c); // 获取线程池中线程数量
// 1、线程数大于CAPACITY(线程池中最大限度的线程数量,maximumPoolSize 也必须小于该值)
// 或者
// 2.1、如果该方法是在线程数小于核心线程数时调用(core 为 true),
// 并且现在线程数大于等于核心线程数(说明这期间有新的线程被创建,导致线程数达到了核心线程数),
// 那直接返回false,因为这时候任务需要进入队列。
// 2.2、如果线程数达到了最大线程数,那更不用创建了,直接返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 到这里,说明可以创建线程了,通过 cas 并发更新线程数
// 更新成功,则跳出该循环,否则就继续重试
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
}
}
// 到这里,开始创建线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 线程池中线程都被包装成 Worker(工人嘛,干活的)
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());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 添加到 workers 中(HashSet)
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 如果线程创建成功,直接启用线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker()
在创建线程前,需要检查线程池的状态以及池中线程的数量等,只有符合条件才开始创建线程。线程被包装成Worker
,这就很形象了。创建成功后,调用线程的start()
方法启动线程,开始执行firstTask
(如果不为 null 的话)的 run 方法中的逻辑。
Worker
这里补充一点线程的知识,我们都知道线程启动后就开始执行 run 方法中的代码了,如果你看了 run 方法的源码,就该明白其实执行的是Runnable
中 run 方法的内容:
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
如果按照这个思路,addWorker() 中最后创建的线程启动后就应该开始执行 firstTask 的 run 方法了。然后一切并没有这么简单,我们先从 Worker 的定义开始
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;
/** Thread this worker is running in. Null if factory fails. */
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);
}
通过 Worker 的构造方法可知,addWorker() 中创建的线程内部的 target 并不是 firstTask,而是包装它的那个 worker:this.thread = getThreadFactory().newThread(this);
。而 Worker 刚好实现了 Runnable 接口。既然这样,线程启用后执行的就是 worker 中的 run 方法,如下
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
所以找了一圈才到执行任务的方法,进入 runWorker()
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
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 {
processWorkerExit(w, completedAbruptly);
}
}
如果线程池启动后第一次执行任务,这时候 firstTask 不为空(这里只是举个例子),则直接执行这个任务。任务为空,则调用 getTask() 获取任务。
getTask()
源码如下
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?
// allowCoreThreadTimeOut:是否允许核心线程超时,默认为 false,
// wc > corePoolSize:当线程数大于核心线程数,对于多余的线程需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// timed 为 true,说明此时线程数大于核心线程数,
// 调用 workQueue.poll() 获取任务,等待 keepAliveTime 时间后还没有任务,线程需要被回收掉
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
从任务队列中拿任务分为两种情况:
- 线程数量 > 核心线程数,在从任务队列中拿(poll)任务超时(keepAliveTime)后,方法返回 null;
- 线程数量 <= 核心线程数,从队列拿任务,如果队列为空,线程阻塞(take()),直到拿到任务为止。
第一种情况,线程等待任务超时后,将结束 runWorker() 方法中的 while 循环,执行 finally 中的processWorkerExit(w, completedAbruptly);
方法:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// ...
workers.remove(w);
// ...
}
从 workers 中移除该线程,方法执行结束后,worker 中线程将被 JVM 回收。
最佳实践
下面总结了工作了关于线程池的一些最佳实践
线程池大小
并发经典《Java 并发编程实战》中提到,线程池的大小应该根据应用场景区分:CPU 密集型和 IO 密集型。
- CPU 密集型:线程数 = CPU 的核数;
- IO 密集型:线程数 = 2 * CPU 核数 + 1;