线程池的基础介绍和使用在上一篇文章中已经介绍,本文从实际使用的角度进行源码的分析,下面进入正文。
1、整体介绍
我们常用的线程池框架如上所示,其中的核心是ThreadPoolExecutor,无论是我们常用的线程池还是定时线程池的创建都和该类有关,该类的整体结构如下:
第一个模块是类的静态和非静态变量,第二个模块是重载的构造方法,我们使用的第一步也是调用这些方法构造我们自己的线程池,第三个模块类的方法,包括运行、终端等(共用的线程池方法以模板设计模式封装在了父模块AbstractExecutorService中),第四个模块是类的属性方法模块,通过这些封装的属性方法,我们可以获取线程池的运行状态以及拒绝策略等。
额外知识;区分属性变量与属性方法的区别,简单来说前者是变量,后者是类似set/get的方法
2、分模块源码分析
线程池的运行状态由变量进行监控,流程由方法执行,底层的实现者是Worker内部类,所以下面先介绍ThreadPoolExecutor的变量和Worker内部类的源码,而由于ThreadPoolExecutor的方法太多,不太方便一一介绍,后面会根据一个实际应用的案例来进行源码的追踪分析。
2.1、ThreadPoolExecutor常见变量
//线程池主要的控制状态变量,是一个32位整数,低29位表示运行线程数、高3位表示运行状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//表示统计线程数的bit位有多少个,此处是29
private static final int COUNT_BITS = Integer.SIZE - 3;
//表示线程池中最大的线程容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//线程池的五种状态(尽量了解下述各种状态的含义和转换的形式,这个在理解后面的线程池调度原理时比较关键)
/*
* RUNNING -> SHUTDOWN 在调用showdown方法时切换状态
* (RUNNING or SHUTDOWN) -> STOP 在调用shutdownNow方法时切换状态
* SHUTDOWN -> TIDYING 当阻塞队列为空,且线程池中没有运行线程时切换状态
* STOP -> TIDYING 当线程池位为空的时候,切换状态
* TIDYING -> TERMINATED 当terminated钩子函数被调用时切换状态
*/
private static final int RUNNING = -1 << COUNT_BITS; //运行态、接收和处理任务
private static final int SHUTDOWN = 0 << COUNT_BITS; //关闭态、不接受新任务但处理已有任务
private static final int STOP = 1 << COUNT_BITS; //停止态、不接受新任务不处理队列中任务,正在运行的任务设置其中断状态为true(不代表线程不运行了)
private static final int TIDYING = 2 << COUNT_BITS; //TIDYING态、所有任务终止,运行线程数为0
private static final int TERMINATED = 3 << COUNT_BITS; //终止态、终止方法terminated()被TIDYING调用后的状态
/**
* 阻塞队列、创建线程池时由用户传入
*/
private final BlockingQueue<Runnable> workQueue;
/**
* 主锁、涉及workers集合及其相关操作时要加的锁。可简单理解为线程池锁,线程池中涉及线程变动的方法通过该锁保证并发安全
*/
private final ReentrantLock mainLock = new ReentrantLock();
/**
* 存储工作线程的集合,访问时需要加mainLock锁
*/
private final HashSet<Worker> workers = new HashSet<>();
/**
* 从mainLock主锁延伸出的条件,用于等待指定时间中断方法(用的较少,了解即可)
*/
private final Condition termination = mainLock.newCondition();
/**
* 线程池历史最大线程数
*/
private int largestPoolSize;
/**
* 已完成任务数
*/
private long completedTaskCount;
/**
* 线程工厂(用的较少,了解即可)
*/
private volatile ThreadFactory threadFactory;
/**
* 拒绝策略
*/
private volatile RejectedExecutionHandler handler;
/**
* 非工作线程最大存活时间
*/
private volatile long keepAliveTime;
/**
* 默认为false,即是否允许核心线程超时,如果允许,则核心线程空闲时间超时后也会被回收
*/
private volatile boolean allowCoreThreadTimeOut;
/**
* 核心线程数
*/
private volatile int corePoolSize;
/**
* 最大线程数
*/
private volatile int maximumPoolSize;
/**
* 创建线程池没有指定拒绝策略时使用的默认拒绝策略
*/
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
通过变量可以看到,线程池的的运行状态以及线程池本身的指标参数
2.2、worker内部类介绍
先来看一下worker类名称信息,Worker类继承了AQS类,内部简化了锁的获取和释放,且功能实现为不可冲入锁。之所以自定义锁功能并且使用不可重入锁而不是可重入锁,我们来看一下官方的解释: This class opportunistically extends AbstractQueuedSynchronizer to simplify acquiring and releasing a lock surrounding each task execution. This protects against interrupts that are intended to wake up a worker thread waiting for a task from instead interrupting a task being run. We implement a simple non-reentrant mutual exclusion lock rather than use ReentrantLock because we do not want worker tasks to be able to reacquire the lock when they invoke pool control methods like setCorePoolSize。我的理解是之所以自定义简化锁的获取和释放主要是为了使得AQS的功能更偏注与唤醒等待线程而不是中断正在运行的线程;而之所以使用不可重入锁,是因为防止工作线程运行中以可重用的方式调用多个线程池中控制类的方法。源码如下:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* 序列号,几乎用不到
*/
private static final long serialVersionUID = 6138294804551838833L;
/** 工作线程 */
final Thread thread;
/** 当前工作线程执行的第一个任务,有可能为null */
Runnable firstTask;
/** 当前线程完成的任务数 */
volatile long completedTasks;
/**
* worker构造器
*/
Worker(Runnable firstTask) {
setState(-1); // 线程刚创建时不支持中断,所以设置AQS的状态为-1,再调用runWorker时再进行修改使得其支持中断,该类最后的中断方法也可以看到,设置中断前会先判断AQS的状态
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** 委托外部类的runworker方法实现线程的循环运行 */
public void run() {
runWorker(this);
}
/** 0表示锁住;1表示未所住 */
protected boolean isHeldExclusively() {
return getState() != 0;
}
/** 尝试获取锁,具体细节参考锁系列的AQS源码分析 */
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/** 尝试释放锁,具体细节参考锁系列的AQS源码分析 */
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
/** 运行时中断线程的方法,一般在调用shutdownNow方法时会调用 */
void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } }
}
可以看到worker主要是起到一个封装的作用,实际的运行还是在外部类ThreadPoolExecutor中,下面跟随一个线程池的实际应用来追踪一下源码
2.3、实例追踪源码
实例使用代码如下:
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10,15,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));
poolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.print("**********start*********");
try {
Thread.sleep(1000*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("**********end*********");
}
});
}
实例代码很简单,创建一个线程池,核心线程数10、最大线程数15、最大空闲时间60秒、容量100的有界阻塞队列,并提交一个任务。创建线程池源码很简单,就是初始化一些变量值以及获取线程工厂,这里跳过,直接进入任务提交执行的核心环境
public void execute(Runnable command) {
//1、如果提交任务为空,直接抛出异常
if (command == null)
throw new NullPointerException();
//2、获取完整的32位控制变量信息
int c = ctl.get();
//3、如果运行线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//3.1、if语句中创建核心线程执行任务,成功则返回,不成功则重新获取完整的控制变量信息
if (addWorker(command, true))
return;
c = ctl.get();
}
//4、如果线程池正在运行且任务入队成功,则二次判断线程池运行状态(能到该语句,表明运行线程数已经大于等于核心线程数)
if (isRunning(c) && workQueue.offer(command)) {
//4.1、重新获取控制变量信息
int recheck = ctl.get();
//4.2、如果线程池不在运行态,且移除任务成功,则调用拒绝策略(二次检查进而判断任务入队过程中线程池是否被关闭)
if (! isRunning(recheck) && remove(command))
reject(command);
//4.3、如果线程池中运行线程数为0,新建一个初始任务为空的非核心线程处理任务(能到这一步表明线程池在运行态或者移除任务失败,所以此时要新建一个线程用于处理队列中新添加的任务)
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//5、如果创建非核心线程失败(能到该语句,表明任务队列已满,且if判断中又尝试创建非核心线程,只有创建失败时才执行拒绝策略)
else if (!addWorker(command, false))
reject(command);
}
可以看到,创建核心以及非核心线程的方法都在addWorker方法中,该方法源码如下:
private boolean addWorker(Runnable firstTask, boolean core) {
//1、for循环判断能否新增工作线程worker
retry:
for (;;) {
//1.1、获取完整控制变量信息,并根据后3位计算线程池运行状态
int c = ctl.get();
int rs = runStateOf(c);
//1.2、运行状态大于0 或者 运行态等于0但初始化任务为空或队列为空 返回创建失败
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//1.3、for循环判断线程数是否合规以及通过CAS方式增加线程数
for (;;) {
//1.3.1、计算线程线程池中线程数,如果线程数大于等于池最大容量,或者大于等于创建线程池时设置的核心线程或最大线程数,则返回创建线程失败。
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//1.3.2、CAS方式扩展线程数,如果成功则直接跳出最外层的循环。进入具体的线程创建阶段
if (compareAndIncrementWorkerCount(c))
break retry;
//1.3.3、上一步CAS增加线程数失败,则重新获取线程池运行状态,如果状态改变,则从最外层for循环重新判断,如果状态未变则在当前循环重新判断和新增
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//2、定义worker增加、启动是否成功的变量
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//3、传入初始任务创建worker (初始任务可以为空,且创建过程中线程工厂会在worker对象中创建一条线程,它是实际执行任务的线程,这也是有的时候称呼worker为工作线程的原因)
w = new Worker(firstTask);
final Thread t = w.thread;
//4、在worker中线程创建成功的情况下,进入如下判断
if (t != null) {
//4.1、获取主锁并加锁(所有涉及workers集合的操作都要加该锁)
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//4.2、重新获取线程池运行状态,防止加锁过程状态变化
int rs = runStateOf(ctl.get());
//4.3、如果线程池处于运行态,或者在shutdown状态(该状态不接受新任务,所以初始任务肯定是空)
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//4.4、判断worker中工作线程是否已经启动,如果启动则抛出异常,因为初始创建时线程不应该被启动,只有后面的if (workerAdded)判断中才能被启动
if (t.isAlive())
throw new IllegalThreadStateException();
//4.5、将worker放入集合、并更新历史最大线程数变量值、以及更新worker新增成功的标识
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//4.6、如果worker新增成功,则启动worker工作线程,并更新worker启动的标识。
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//5、如果worker启动失败,尝试从workers集合中移除当前可能插入的worker,并CAS减少一开始双for循环中增加的线程数。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
至此,所有的线程创建启动源码都已经看了一遍,是不是还纳闷,怎么没有线程池处理任务的源码。回忆下刚才的代码,我们启动了worker中的线程,所以这个线程处理了任务,因此下面来看一下worker中的run方法。
/** 委托外部类的runWorker方法执行处理功能 */
public void run() {
runWorker(this);
}
限于篇幅原因,以及后续内容比较多,所以新开一篇进行编辑