在分析源码之前需要先介绍一些基本概念:线程池的几种创建方法及线程池中一些重要变量,有基础的可以跳过,直接从第3步开始看源码分析。
1、线程池主要有四种创建方方法:
Executor提供的三种静态方法:
// 使用核心线程及同步队列,效率高,但是消耗CPU
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 使用指定线程数及链表队列,效率不太高,大量并发会造成内存溢出
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 使用单个线程及链表队列,效率最低,大量并发会造成内存溢出
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 另外一种是自定义线程池:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
自定义线程池中7个参数的意思:
corePoolSize:核心线程数
maximumPoolSize:最大线程数(非核心线程数 = 最大线程数 - 核心线程数;核心线程数不够用,队列也塞满任务后会创建非核心线程执行任务)
keepAliveTime:非核心线程停止工作后存活时间
unit:时间单位,配合keepAliveTime使用
workQueue:阻塞队列(常用队列有SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue)
threadFactory:线程工厂,可自定义线程(默认实现:Executors.defaultThreadFactory())
handler:拒绝策略(默认实现:AbortPolicy)
2、线程池中的一些重要方法及变量:
// 线程池对象,二进制全长32位,高3位记录线程池状态,低29位记录线程池中线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 常量29,方便后边做位运算
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池容量,也就是线程池中的最大线程数
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// 111 代表线程处于运行状态,可正常接收并执行任务
private static final int RUNNING = -1 << COUNT_BITS;
// 000 代表线程处于SHUTDOWN状态,不接受新任务,但是会处理正在执行的任务及阻塞队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001 代表线程处于停止状态,不接受新任务,停止正在执行的任务并且也不会执行阻塞队列中的任务
private static final int STOP = 1 << COUNT_BITS;
// 010 代表线程处于即将销毁状态,这是SHUTDOWN及STOP向TEMMINATED过渡的一个中间状态
private static final int TIDYING = 2 << COUNT_BITS;
// 011 代表线程终止状态,处于TIDYING状态的线程调用terminated方法变为TERMINATED
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取线程池状态
private static int runStateOf(int c) { return c & ~COUNT_MASK; }
// 获取正在工作的线程数
private static int workerCountOf(int c) { return c & COUNT_MASK; }
OK,了解这些后,看源码就容易多了。
3、execute源码分析
public void execute(Runnable command) {
// 健壮性判断,传递任务为空,直接返回空指针异常
if (command == null)
throw new NullPointerException();
// 线程池对象的32位int值
int c = ctl.get();
// 1、如果工作线程数 < 核心线程数
if (workerCountOf(c) < corePoolSize) {
// 创建核心线程
if (addWorker(command, true))
return;
// 创建核心线程失败的话,这里会重新获取线程池对象的32位int值,因为存在并发情况
c = ctl.get();
}
// 2、先判断线程池是否处于运行状态,若处于运行状态,则将任务加入队列
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取线程池对象的32位int值,还是因为并发存在,线程池状态及线程池中线程数量随时会有变动
int recheck = ctl.get();
// 若线程池非运行状态,直接将任务移除队列
if (! isRunning(recheck) && remove(command))
// 移除队列失败,直接执行拒绝策略
reject(command);
// 若线程池处于运行状态,但是线程池中没有工作线程了
else if (workerCountOf(recheck) == 0)
//这时候就创建一个空任务的非核心线程(用于执行第2步中加入的那个任务)
addWorker(null, false);
}
// 3、创建一个非核心线程
else if (!addWorker(command, false))
//创建失败,执行拒绝策略
reject(command);
}
代码中已经做了详细注释,基本比较清晰了,这里再总结一下流程:
1、任务进来,先创建核心线程执行任务,若核心线程数已经达到了上线,执行第2步
2、将任务放入阻塞队列(中途由于并发原因,线程池状态随时可能会被改变,会做一些相应的处理,执行拒绝策略呀,或者创建空任务线程,执行队列中的任务)
3、若阻塞队列中也放满了,就创建非核心线程执行任务,若线程数也已经达到了上线,就执行拒绝策略。
workQueue.offer(command)方法是进行入队操作的,就是把任务添加到对应的队列中,不同队列(SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue)的入队方式有所不同,不是重点,这里就不展开讲了,有兴趣的可自行百度。
这三步中都有一个方法addWorker,下边来分析这个方法
4、addWorker源码分析:创建线程执行任务
private boolean addWorker(Runnable firstTask, boolean core) {
// 标示位,用于从内部循环中直接跳转到这里
retry:
// 下边的两个死循环就是为了修改工作线程的数量。
for (int c = ctl.get();;) {
// 这个方法中判断是否 c >= SHUTDOWN,也就是判断线城池是不是非运行状态(运行转态是-1,SHUTDOWN是0)
if (runStateAtLeast(c, SHUTDOWN)
// 若线程池处于运行状态,可直接跳过,不会进入到此处。
// 非SHUTDOWN转态,或添加任务非空,或任务队列为空
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
// 进入到此处的情况总结:
// 1、线程池处于STOP、TIDYING、TERMINATED状态。
// 2、线程池处于SHUTDOWN转态,此时传递进来的任务不为空
// (那么传递的任务为空时为什么不返回而是继续往下走去创建线程呢,上边我们在分析execute方法时,
// 第2步提到了任务加入队列后,若没有工作线程,则会创建一个空任务的线程来执行任务,
// 所以空任务就是这么由来的,这里不应该卡掉,需要创建一个空任务的线程去执行队列中的任务)
// 3、线程池处于SHUTDOWN转态,此时传递进来的任务为空,
// 但是任务队列中已经没有需要执行的任务了,就不需要走下边的方法去创建线程了。
return false;
for (;;) {
// 这里区分要创建核心线程还是非核心线程,判断线程池总数量是否达到了指定上限
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 通过CAS方法修改工作线程数
if (compareAndIncrementWorkerCount(c))
// 修改成功结束外层循环
break retry;
c = ctl.get();
// 重新获取线程池状态后再次判断线程池是否非运行转态
if (runStateAtLeast(c, SHUTDOWN))
// 若非运行状态直接跳到外层循环,继续以上步骤
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);
final Thread t = w.thread;
// 这是给任务创建工作线程时从线程工厂获取的线程,几乎不会为空
if (t != null) {
// worker继承了AQS,这里就是获取锁
final ReentrantLock mainLock = this.mainLock;
// 加锁,防止多线程同时修改workers及largestPoolSize的值
mainLock.lock();
try {
int c = ctl.get();
// 线程池处于运行状态
if (isRunning(c) ||
// 或线程池处于SHUTDOWN转态,切任务为空(这里以上有分析,不再解释)
(runStateLessThan(c, STOP) && firstTask == null)) {
// 判断一下线程是否已经启动(健壮性判断,我的线程刚创建还没调用start启动,错此时线程已经启动,直接报错)
if (t.isAlive())
throw new IllegalThreadStateException();
// 将工作线程加入到集合中(用于统计数量)
workers.add(w);
// 工作线程数量
int s = workers.size();
// largestPoolSize记录的是最大的工作线程数,若此时的workers数量大于了这个值,就替换一下。
if (s > largestPoolSize)
largestPoolSize = s;
// 记录工作线程已经加入到结合中了
workerAdded = true;
}
} finally {
// 释放锁
mainLock.unlock();
}
// 若工作线程已加入集合中
if (workerAdded) {
// 启动线程
t.start();
// 记录线程已启动
workerStarted = true;
}
}
} finally {
// 若工作线程未启动,就执行加入队列失败方法
if (! workerStarted)
// 主要就是将worker从workers中移除,线程池中工作线程数减1,以及终止线程
addWorkerFailed(w);
}
return workerStarted;
}
以上就是我对线程池execute方法的源码分析,几乎每一行代码都添加了注释,可自行对照理解。