文章目录
浅谈Java线程池(jdk1.8)
- 本博客来源于自己在看JDK线程池源码时,边看边同步写下的笔记,因此排版基本没有,希望见谅;
- 仅仅只是分析了比较核心的实现,限于个人目前的水平,尚有不理解之处,写的内容也可能有差错,非常乐意接受您的指正与建议;
- 对于线程终止,
shutdown()
与shutdownNow()
我觉得没必要说,因为源代码很清晰,阅读者这部分代码也没什么难度,因此没有说这一部分
线程池状态部分
- 使用一个
AtomicInteger
类型的ctl
来存储wokerCount
和线程池运行状态runSate
,其中高3位用来存储runState
,而低29位存储wokerCount
,因此在jdk1.8的设计当中,有效线程数量最多是229-1,而不是231-1; - 线程池有五种运行状态:
- RUNNING:能够接受新的任务并且能够处理队列中的任务。数值为0xE0 00 00 00(也就是高三位为1,后29位为0);
- SHUTDOWN:不能接受新的任务,但是还能处理队列中的任务。数值为0x00 00 00 00;
- STOP:不能接受新的任务,也不能处理队列中的任务,等待终止正在进行中的任务。数值为0x20 00 00 00;
- TIDYING:所有的任务都已经终止,
wokerCount
等于0。数值为0x40 00 00 00; - TERMINATED:
terminated()
方法已经完成,则进入该状态。数值为0x50 00 00 00;
- 之所以这样安排线程池的运行状态的数值,是为了方便通过数值大小的比较,可以发现从数值上来说RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED。线程池的状态只可能这样转换:
- RUNNING -> SHUTDOWN(显式调用
shutdown()
或者隐式地通过finalize()
方法来自动调用; - RUNNING or SHUTDOWN -> STOP(显式调用
shutdownNow()
) - SHUTDOWN -> TIDYING(当线程池为空)
- TIDYING -> TERMINATED(当
terminated()
钩子方法调用结束,terminated()
方法是一个protected的空方法,可以通过继承来重写该方法,当线程池状态转变为TIDYING时将会调用该方法,该方法调用完成之后线程池就会进入TERMINATED状态,当线程池进入TERMINATED状态时awaitTermination()方法才会返回; - (TEMP)检测线程池是否能从SHUTDOWN进入TIDYING状态有trick,(在队列非空之后可能变为空,在关闭状态下,队列也可能变为空,但是我们只能在wokerCount为0(有时需要重新检查)的情况下才能终止);
- RUNNING -> SHUTDOWN(显式调用
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;
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;
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; }
private static boolean runStateLessThan(int c, int s) {return c < s;}
private static boolean runStateAtLeast(int c, int s) {return c >= s;}
private static boolean isRunning(int c) {return c < SHUTDOWN;}
- 因此可以看到
runStateOf(int c)
实际上就是返回了c
的高三位,因为CAPACITY
数值为0x1F FF FF FF; workerCountOf(int c)
实际上就是返回了c
的低29位,也就是worker的数量;ctlOf(int rs,int wc)
实际上就是将runState
与wokerCount
组合起来的一个整数返回,也就是ctl的值;- 同时也注意到线程池初始状态为RUNNING,且
wokerCount
初始为0;
构造函数中的参数部分
直接看代码:
private final BlockingQueue<Runnable> workQueue;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private volatile long keepAliveTime;
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) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null?null:AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 关于
corePoolSize
和maximumPoolSize
,当一个任务到达时,线程数量小于corePoolSize
,即便有其它空闲的线程,也会直接创建一个新线程,并且该任务不会入队,而是直接创建线程来处理该任务。当且仅当没有空闲线程并且workQueue
已满的时候才会创建超出corePoolSize
的线程,而keepAliveTime
则指定了超出corePoolSize
数量的线程在处理完任务之后的空闲状态维持的时间,超出该时间则该线程会被销毁(注:后续这里最好用实际的代码来说明) workQueue
存储execute
提交的任务,如果处理任务的线程数量小于corePoolSize
,那么该任务不会入队而是尝试直接创建一个新线程来处理该任务threadFactory
提供了创建线程的方式,如果没有指定,则会提供一个默认的线程工厂,并且所有线程都属于同一个threadGroup,非守护线程,具有相同的优先级等,如果希望自定义这些参数,则需要自己提供一个实现了ThreadFactory
接口的自定义的线程工厂handler
定义了如何处理在线程池调用了shutdown()
方法之后提交的任务的策略,通常有四种方式(这里就不再赘述,可自行查看文档,其实通过名字也能大概知道这些策略的意思了):ThreadPoolExecutor.AbortPolicy
(默认策略)ThreadPoolExecutor.CallerRunsPolicy
ThreadPoolExecutor.DiscardPolicy
ThreadPoolExecutor.DiscardOldestPolicy
线程池提供的hook function
- 线程池还提供了几个未实现的空的钩子函数,通过继承线程池类并且重写这些方法就能创建一个在功能上实现了扩展的自定义线程池,这些方法有:
beforeExecute(Thread, Runnable)
afterExecute(Runnable, Throwable)
terminated()
- …
初探execute(Runnable command)
方法
当我们指定一系列参数实例化一个线程池对象之后,通常就会调用execute方法来提交任务,因此,我也以这样的顺序来分析。直接上代码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 在下方之所以多次查询线程池的运行状态,完全就是因为并发所要求的,因
// 为没有同步机制,所以无法确保下一时刻的状态和判断时查询的状态一致,
// 但是实际上这种方法似乎并不能完全确保得到的状态是正确的,只是可能在
// 这个系统的设计上认为这样的差异是可以忍受的,完全没有必要为了确保得
// 到的状态正确而使用锁,导致性能上的下降
int c = ctl.get();
// 若wokerCount小于corePoolSize,则直接尝试创建新线程(core型)来处理,
// 线程创建成功则会直接返回
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 若线程池处于运行状态并且向workQueue中提交任务成功,则再一次检查线程池
// 的运行状态,若此时线程池不再处于运行状态了(!isRunning(recheck)),就尝试
// 从workQueue中移除该任务,若移除成功,则调用reject(command)方法,该方法
// 实际上就是调用了handler中的rejectedExecution方法,通过提供不同的饱和策略
// RejectedExecutionHandler,可以有不同的处理方法。相反,若此时系统仍处于运行状态
// 并且工作线程数量为0时,创建一个新线程
// 相反,若任务入队失败,则尝试创建一个线程(没有设置core为true,则只会检查是否
// 运行线程数量是否已达上限),若失败,则表明此时线程池已经shutdown或者饱和了
// (并不是指队列满了而是线程数量已达上限),因此直接调用reject(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);
}
else if (!addWorker(command, false))
reject(command);
}
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
- 可以看到,注释说得就已经非常地清楚了,首先
ctl
是一个AtomicInteger
类,因此保证了原子性与可见性,可以看到,当woker的数量小于corePoolSize
的时候,会尝试直接创建新线程,即addWorker(command,true)
,true标记该线程是core线程,core线程即便空闲了也不会被销毁,如果成功创建线程处理该command,那么函数直接返回。 - 其实接下来的思路很明显了,根据我们使用线程池的方式,初始化之后提交任务即可创建线程,运行线程,实现线程复用,因此想要了解线程池是如何实现线程复用的,那么关键就必然在worker以及addWorker中,因此,虽然在ThreadPoolExecutor中有那么多函数,但是就我们的目的而言,其它函数基本就可以不看了。
线程池的核心:Worker
// Worker的部分定义,对于Woeker中定义的其它函数,不需要去了解是怎么实现的
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;
Runnable firstTask;
// 统计该worker成功执行的任务数,线程封闭,为线程内部的局部变量,不会产生安全性问题
volatile long completedTasks;
// 可以看到worker创建之后即持有一个线程工厂产生的线程对象,并且该线程运行的
// 就是worker的run方法,而worker的run方法实际调用的是线程池的runWorker方法
// 因此实现线程复用的关键必然就在runWorker方法之中
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//
public void run() {
runWorker(this); // 传递该worker本身
}
}
// !!!线程池实现线程复用的核心函数!!!
// Worker的run方法实际上所调用的函数,删掉了部分异常处理,为了更清楚地看到运行逻辑
final void runWorker(Worker w) {
Thread wt = Thread.currentThread(); // 获取运行线程
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 结合对之前execute的分析,worker初始化可能有任务,可能没有
// 因此这里可以看到,这里判断该worker初始是否有task,初始没有
// task就尝试从wokerQueue中获取task,这其实就是实现线程复用的
// 关键,创建的线程就是消費者,从生产者workQueue中获取任务来执行
// 执行完毕之后继续循环;
// !!!但是考虑这样一种情况,task为null并且在调用getTask时也为null
// 当你查看getTask函数源码时,你也会发现,当workeQueue为空时
// getTask函数中就会调用compareAndDecrementWorkerCount,并且返回null,
// 结合这里的while循环你就懂了,若getTask返回null,则该worker线程必然
// 就结束while循环终止了,那么就可能使线程池中的worker数量小于
// corePoolSize,这显然不对,因此processWorkerExit函数中就会对这个
// 情况进行处理
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); // 钩子函数,默认没有实现
Throwable thrown = null;
task.run(); // 由我们提交的任务的run方法在这里被调用
// 注:实际上这里是有try..catch块的,这里删掉了,方便看代码
// 在这里的try..catch..finall块中的finally方法实际还调用了
// afterExecute(wt,task)钩子函数
} finally {
task = null;
w.completedTasks++; // 该worker完成的任务数加1
w.unlock();
}
}// while
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 查看runWorker方法
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();
}
tryTerminate();
int c = ctl.get();
// 在RUNNING,SHUTDOWN状态下,仍然要维护worker的数量,否则
// 可能有这种情况发生:即所有worker线程都已经进入终止状态,但是
// workQueue中的任务还没有处理完,相反,如果线程池已经进入STOP状态
// 那么就只需要等待所有任务执行完毕即可
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
// 可以看到线程池至少会维护一个worker来执行任务
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 运行线程数量大于min,则无需创建新线程以达到线程池中线程数的最低要求
if (workerCountOf(c) >= min)
return;
}
// 可以看到这里还能够增加worker,正是解决了在runWorker中所提到的问题
addWorker(null, false);
}
}
- 到了这里,可能会有疑问,线程只有在调用
start
方法之后才算开启线程,那么start方法是在哪里调用的,其实很显然,是在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.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
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 {
w = new Worker(firstTask); // 创建新worker
final Thread t = w.thread; // 获取worker持有的线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // workers是线程池中的一个HashSet,用来存储Worker
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;
}
理清脉络
通过上面的分析,对线程池实现线程复用的方式以及线程的创建与维护的逻辑基本上就理个大概了,下面就从提交任务开始,从头理一下整个程序大致上运行的逻辑(忽略了很多细节,只是为了理清脉络):
提交任务execute -> 调用addWorker或者将任务入队(workQueue) -> 创建Worker对象,该Worker对象持有一个线程对象,并且该线程对象最终调用的就是该Worker对象的run方法,而Worker对象的run方法中就仅仅只是调用了线程池的成员函数runWorker -> runWorker就是消費者,向生产者workQueue索要任务,拿到任务之后,就调用该任务的run方法(这个就是由我们初始向execute中传递的Runnable对象的run方法) -> 执行完毕之后再向workQueue索要任务
所以这就是实现线程复用的基本原理,其实单纯考虑原理很简单,不是很准确地说(个人理解):就是采用生产者-消费者的模式,创建消费者线程,在该线程中使用一个while循环向生产者workQueue索要任务执行。