jdk1.8源码中的UML
接口Executor
此接口提供了一种将任务提交与每个任务的运行机制分离的方法
void execute(Runnable command);
接口ExecutorService
-
提供两个关闭ExecutorService的方法。关闭后,没有在执行的任务,也没有等待执行的任务且新任务无法提交。此时ExecutorService资源将被回收。
- shutdown方法,关闭前会执行已提交的任务
- shutdownNow方法,会阻止已提交等待执行的任务,并且会尝试关闭正在执行的任务。
-
扩充执行任务的能力。
- submit方法,future方式用来等待任务执行结果
- invokeAny方法,执行任何一个任务
- invokeAll方法,执行一批任务
抽象类AbstractExecutorService
提供ExecutorService执行方法的默认实现,将执行任务的流程串起来,下层只需关注任务的实现。
ThreadePoolExecutor
实现最复杂的运行部分,一方面
维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
ThreadePoolExecutor运行机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pg9VCUOI-1655452866388)(https://github.com/wangjinwengithub/-/blob/master/%E7%BA%BF%E7%A8%8B%E6%B1%A0/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%A8%A1%E5%9E%8B.drawio.png?raw=true)]
线程池5种状态
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;//所有任务都已终止,workCount(有效线程数)为0
private static final int TERMINATED = 3 << COUNT_BITS;//terminated()方法执行完以后变成该状态
RUNNING为-1左移29位。即:
1000 0000 0000 0000 0000 0000 0000 0001 左移29位得
1001 0000 0000 0000 0000 0000 0000 0000
SHUTDOWN为0左移29位。即:
0000 0000 0000 0000 0000 0000 0000 0000 左移29位得
0000 0000 0000 0000 0000 0000 0000 0000
STOP为1左移29位。即:
0000 0000 0000 0000 0000 0000 0000 0001 左移29位得
0001 0000 0000 0000 0000 0000 0000 0000
TIDYING为2左移29位。即:
0000 0000 0000 0000 0000 0000 0000 0010 左移29位得
0010 0000 0000 0000 0000 0000 0000 0000
TERMINATED为3左移29位。即:
0000 0000 0000 0000 0000 0000 0000 0011 左移29位得
0011 0000 0000 0000 0000 0000 0000 0000
当前运行线程数
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
private static int workerCountOf(int c) { return c & CAPACITY; }
先来看一下初始c是多少
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
c:
1001 0000 0000 0000 0000 0000 0000 0000 |
++0000 0000 0000 0000 0000 0000 0000 0000++
1001 0000 0000 0000 0000 0000 0000 0000
CAPACITY为1左移29位再减去1。即:
0000 0000 0000 0000 0000 0000 0000 0001 左移29位得
0001 0000 0000 0000 0000 0000 0000 0000 -1得
0000 1111 1111 1111 1111 1111 1111 1111
这个时候就可以算出初始运行线程数
c|CAPACITY
1001 0000 0000 0000 0000 0000 0000 0000 &
++0000 1111 1111 1111 1111 1111 1111 1111++
0000 0000 0000 0000 0000 0000 0000 0000
再来看一下运行状态的计算
private static int runStateOf(int c) { return c & ~CAPACITY; }
~CAPACITY 为:
1111 0000 0000 0000 0000 0000 0000 0000
~CAPACITY&c
1111 0000 0000 0000 0000 0000 0000 0000 &
++1001 0000 0000 0000 0000 0000 0000 0000++
1001 0000 0000 0000 0000 0000 0000 0000
分析到这里,基本上可以得出一个结论。
线程池用一个原子操作的int类型来存储线程池状态和运行中线程个数两种数据。采用高4(32 ~ 29)位存储线程池状态,低28(28~1)位来存储运行线程数量。
态间的转换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aIpSbl9f-1655452866389)(https://github.com/wangjinwengithub/-/blob/master/%E7%BA%BF%E7%A8%8B%E6%B1%A0/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E5%9B%BE.png?raw=true)]
当一个任务提交时
- 会先判断当前运行的线程数是否小于核心线程数,小于会尝试创建一个新的线程直接执行。
- 线程数不小于核心线程数或创建新线程失败,会尝试加入到任务队列。
- 加入任务队列失败则拒绝任务。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
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);
}
从源码中可以看到,一个任务提交上来以后,调用的是AbstractExecutorService中的submit方法,将任务包装成future然后调用ThreadPoolExecutor中的execute方法。
submit详细流程图
何时销毁
添加任务时创建一个Worker对象,这个Worker对象执行完当前任务会去轮询阻塞队列,如果阻塞队列里面没有任务,就有可能销毁。
对于核心线程来说,可以无限等待任务执行。非核心线程超时没有获取到任务,会进入销毁流程。
如何销毁
添加线程是吧Worker放入一个set集合中,销毁是把相应的Worker对象从set集合中移除。gc自动回收来达到销毁的目的。