前言
不管是工具类还是复杂的框架,其底层的东西都是可以细化,量化,甚至是形象化地表述出来,只有深入了解其最终的缓存模型,运行流程,才算是真正学到精髓!今天再来深挖一下线程池,搞明白下面几个问题:
- 线程池最核心的实例缓存对象分别是哪些?
- 线程池的线程是如何创建,保存的?
- 线程池是如何扫描对象并运行任务的?
一、线程池状态
为方便看源码,先补充说明一下线程池的状态设计,了解状态设计对看源码有很多帮助;
Java线程池状态设计的很巧妙,其中高3位用来表示线程池状态,后29位用来表示线程数量;因为最高位是符号位,因此线程池的状态最多有7种[-3 ~ 3],实际上只用到了5种,如下:
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; // -536870912
private static final int SHUTDOWN = 0 << COUNT_BITS; // 0
private static final int STOP = 1 << COUNT_BITS; // 536870912
private static final int TIDYING = 2 << COUNT_BITS; // 1073741824
private static final int TERMINATED = 3 << COUNT_BITS; // 1610612736
如图:高3位是状态位,低29位是线程数量;
int占4个字节,共32比特,数据范围:[-2^31~2^31-1] (-2147483648 ~ 2147483647)
二、核心实例对象
1、线程集合:HashSet<Worker> workers
池中用于容纳全部工作线程的Set集合。我们知道线程类是Thread,这个Worker就是包装了Thread的内部类,从下面源码中可以找到Thread实例属性;
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 生成线程,关键是this参数
this.thread = getThreadFactory().newThread(this);
}
}
这个Worker本身就是一个任务类,因为它继承了Runnable接口,它与线程对象相互引用,在线程启动后,就会调用Worker类的run方法,因此,必须要清楚线程池的两种任务:
- 普通任务,放在阻塞队列,负责处理业务逻辑的任务;
- 线程任务,放在Worker集合,负责扫描阻塞队列,拉取普通任务并执行;
——这一点搞懂了,才算是深入了解线程池原理;
除此之外,Worker类中还有个firstTask对象,也是个任务对象,它的作用是在创建Worker的时候,并没有把业务任务放阻塞队列,而是直接交给Worker来执行,这个firstTask就是负责承接初创时的业务任务;
2、阻塞队列:BlockingQueue<Runnable> workQueue
下图是任务的提交流程,阻塞队列提供暂存业务逻辑任务的功能;当然,不是所有的任务都会入阻塞队列,要根据线程池的运行情况和运行状态决定是否入队列;
三、线程池运行流程
先简单写个使用线程池的例子:
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.submit(() -> System.out.println("a"));
}
}
当我们submit一个任务的时候,任务就会被自动执行,那么线程池是如何运行的呢?
跟踪submit方法,在AbstractExecutorService抽象类里面:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
继续跟踪execute:
public void execute(Runnable command) {
// 多余代码先不看,重点就在这个 addWorker 中
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
}
comand是普通任务,下面代码可以看到,在Worder类的构造方法中创建了线程对象,而线程对象的目标任务又是this(看上面2.1章节:ThreadPoolExecutor.Worker内部类的构造方法),也就是上面提到的相互依赖;当新创建Worker的时候,不会把普通任务塞给阻塞队列,而是直接复值firstTask!
private boolean addWorker(Runnable firstTask, boolean core) {
// 同样省略部分代码
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.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;
}
重点来了,上面提到两遍Worker和线程的关系,Worker继承了Runnable接口,那么当启动线程的时候,就会去调用Worker的run方法
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
最终调用到runWorker,这个是线程池的核心运行流程,但是也很简单,run Worker本质上就是执行任务,如果Worker是首次创建的,那么也同时赋值了一个普通任务,直接执行即可,如果不是首次创建的Worker,则需要从阻塞队列中捞取任务;
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 如果是首次创建的Worker,那么firstTask有值,就是普通任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 进入循环,两个条件:
// 1. firstTask非空,就不要从阻塞队列获取,直接运行firstTask
// 2. firstTask为空,需要从阻塞队列获取任务
while (task != null || (task = getTask()) != null) {
w.lock();
try {
// 这是个空方法,用于自定义线程池的时候,自定义处理逻辑
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 运行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} finally {
afterExecute(task, thrown); // 同beforeExecute一样
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
来看捞取任务方法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判断也很重要,如果调用了shutdown/shutdownNow方法,那么“核心线程”也会返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c); // 取线程数
// Are workers subject to culling?
// allowCoreThreadTimeOut = false
// timed = true 表示存在非核心线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// wc > maximumPoolSize = false 一般不会出现这种情况,所以是false
// 这个if条件成立的前提:必须存在非核心线程【关键】
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null; // 这个null就是非核心线程被销毁的罪魁祸首
continue;
}
try {
// 获取任务,但要看当前线程数量是否超过核心线程数,
// 如果超过了,则使用pool方法,否则使用take方法
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r; // 能取到任务就返回,否则设置timedOut进去死循环
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
take:如果队列中没有数据,则线程wait释放CPU;
poll:如果没有数据返回,直接返回null;
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
四、总结
1、线程池提供一个Set集合属性worker用来缓存所谓的线程(其实是线程的一个包装);
2、线程池提供一个workQueue队列用来保存任务;
3、线程包装类Worker也是一个任务类,线程通过Worker不停地执行任务并扫描队列;
4、通过阻塞队列的poll和take方法获取任务,以此让非核心线程在取不到任务后消亡,让核心线程在取不到任务后等待;——这种说法是为了便于理解,实际上并没有严格区分核心与非核心线程,仅仅是以线程数量判断