在多线程开发过程中,会面临以下问题:
而线程池可以解决上述问题:
下面以一个简单的例子说明如何使用线程池:
public class ThreadPoolTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
@Override
public String call() {
return "Hello World";
}
});
executor.submit(task);
System.out.println(task.get());
}
}
根据类图我们可以发现,线程池的最终实现类为ThreadPoolExecutor。实际上还有ScheduledThreadPoolExecutor,而本文是以使用最广泛的ThreadPoolExecutor类进行讲解。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
ThreadPoolExecutor的构造函数运用了重载的方式,可以根据需要很灵活地构造出一个ThreadPoolExecutor类。每个参数的具体说明如下:
通过Executors类方法,我们可以创建种线程池:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
在线程池内部维护了一个属性:ctl。它的高3位用来表示线程池状态,低29位用来保存线程个数。其中线程池的状态如下:
各个状态的转化如下:
* RUNNING -> SHUTDOWN
* On invocation of shutdown() //调用了 shutdown方法
* (RUNNING or SHUTDOWN) -> STOP
* On invocation of shutdownNow() //调用了 shutdownNow方法
* SHUTDOWN -> TIDYING
* When both queue and pool are empty // 当线程池和任务队列都为空时
* STOP -> TIDYING
* When pool is empty //当线程池为空时
* TIDYING -> TERMINATED
* When the terminated() hook method has completed // 当 terminated() hook方法执行完毕后
先说明一下,本文所用的源码是Jdk 11版本,网上很多资料是用的Jdk 8,虽有细微差别,但是影响不大。
正如例子中所调用方法:executor.submit(task),这背后的执行逻辑是什么样子的?
ExecutorService是一个接口,而它的实现类是AbstractExecutorService,以下是AbstractExecutorService中的submit方法,也是入口。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 将 Runnable对象封装成 FutureTask,这样就可以通过 get方法获取调用结果了。
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 调用 ThreadPoolExecutor的 execute方法,提交任务。
execute(ftask);
return ftask;
}
由此可以看出,先将Runnable对象封装成FutureTask对象,然后调用ThreadPoolExecutor的execute方法。execute方法具体如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取ctl的值,高3位是线程池状态、低29位是线程个数
int c = ctl.get();
// workerCountOf(c)是获取当前线程数。
// 如果当前线程数小于核心线程数,则调用 addWorker方法创建一个核心线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 由上方的 return可以判断出,代码执行到这里,说明当前线程数大于等于核心线程数
// isRunning(c)是获取当前线程池的状态
// workQueue.offer(command) 是将任务添加到 workQueue队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次检查线程池状态,防止添加任务到任务队列后,线程池的状态发生了改变
// 如果线程池停止了,则调用 remove方法将该任务从执行队列中移除,
if (! isRunning(recheck) && remove(command))
// 执行拒绝策略
reject(command);
else if (workerCountOf(recheck) == 0)
// 如果线程池未停止,处于running状态,且当前线程池的工作线程数为0,则创建一个新的线程。
addWorker(null, false);
}
// 如果workQueue已经满了,无法放下任务,则新建“非核心线程”来执行该任务。
// 如果也无法通过新建“非核心线程”来执行该任务(当线程数量⼤于maximumPoolSize),则执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}
下面我们便进入addWorker()方法看看线程池是如何新开线程执行任务的。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// 这里源码中注释为:在必要的时候检查 workQueue是否为空。
// 这里的必要的时候是指:
// 1. 线程池状态位为 STOP、TIDYING和 TERMINATED。
// 2. 线程池状态位为 SHUTDOWN并且已经有了一个 firstTask需要执行。
// 3. 线程池状态位为 SHUTDOWN并且队列为空。
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
// 如果 core是 true,那说明该要创建的线程是核心线程
// 判断线程数是否超出限制,如果超出则失败。
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 配合循环,以 CAS的方式添加线程数,成功了则跳出循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果 CAS失败了,则在线程池状态正常的情况下,再次重新尝试重试。
// 因为这个时候可能已经有任务完成了,工作线程数就减少了。
c = ctl.get();
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
}
}
//以上代码是为了对线程状态、线程数量做判断,相当于是准备工作。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建一个 Worker对象
w = new Worker(firstTask);
// 实例化一个 Thread对象
final Thread t = w.thread;
if (t != null) {
// 加上独占锁,这里是为了防止有多个线程调用线程池的 execute方法
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 重新检查线程池状态,避免在获取独占锁之前有其他线程调用了 shutdown方法
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
// 添加任务。需要注意一下,这里是往 HashSet workers中添加任务,而非 workQueue。
// workers指的是正在工作的线程集合,而workQueue是等待执行的阻塞队列。
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;
}
正如我们调用一个 Thread类的 start方法,在新建线程获得 CPU时间后,就会调用 run方法。下面我们来看一下 Worker类。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
// 将 Runnable对象装饰为 Worker对象
Worker(Runnable firstTask) {
// 将线程的 state设置为 -1,以防线程被中断。
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
// 这里少一个 @Override注解,所以看的不大明显,其实在新建线程获得 CPU时间后就是调用的该方法。
public void run() {
// 实际执行委托给外部的 runWorker方法
runWorker(this);
}
}
那么,下面便可以进入runWorker方法了
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 将线程的 state设置为 0,这样当调用线程池的 shutdownNow方法时就可以中断 Worker线程了。
w.unlock();
boolean completedAbruptly = true;
try {
// 这个循环是关键所在!!!
// 如果当前的 firstTask为空,便会调用 getTask方法去从阻塞队列 workQueue中获取任务。
while (task != null || (task = getTask()) != null) {
w.lock();
// 再次检查线程池状态,如果线程池处于中断状态,当前线程将中断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
// 这里便是我们需要执行的业务逻辑代码。
task.run();
// 任务结束后需要执行的业务逻辑代码。
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 执行清理工作,主要是统计整个线程池完成的任务个数,并从工作集合中删除当前 Worker。
// 另外判断当前线程个数是否小于 corePoolSize,如果是,则增加线程数。
processWorkerExit(w, completedAbruptly);
}
}
那么问题来了,线程池是如何实现线程复用的呢,实际上就是上述循环判断所调用的getTask()方法中。
private Runnable getTask() {
boolean timedOut = false;
// 注意这里是一个循环
for (;;) {
int c = ctl.get();
// 依旧是在必要的时候检查 workQueue是否为空。
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 获取正在工作的线程数
int wc = workerCountOf(c);
// allowCoreThreadTimeOut变量是指的是否允许核心线程空闲超时。
// allowCoreThreadTimeOut默认是 false,也就是核心线程即使空闲也不会被销毁。
// 如果 allowCoreThreadTimeOut为 true。核心线程在 keepAliveTime内仍空闲则会被销毁。
// allowCoreThreadTimeOut为 false,那么如果正在运行的线程数大于核心线程数,那么 timed则为 true,否则为 false。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 在以下情况下递减 worker数量:
// 1. 如果正在工作的线程数超过了最大线程数,但是缓存队列已经空了。
// 2. 如果有设置允许线程超时或者线程数量超过了核心线程数量,并且线程在规定时间内均未 poll到任务且队列为空。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 根据 timed状态从workQueue中获取任务。
// 实际上就是如果正在运行的线程数大于核心线程数,就执行 poll方法,该方法设定了超时时间。
// 如果正在运行的线程数小于等于核心线程数,就执行 take方法,如果workQueue为空,则当前线程就会阻塞,直到成功获取任务为止,那么核心线程就不会被销毁了。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}