Java-Java中的线程池原理分析及使用
线程是调度CPU资源的最小单位,线程模型分为KLT模型与ULT模型。
JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里有一个对应的线程 。
[并发编程] - 操作系统底层工作原理
使用new Thread 创建500个线程
public static void main(String[] args) {
for (int i = 0; i < 500; i++) {
new Thread(() -> {
while (true){
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
观察OS的线程数量的增长情况 。
,停止后,再观察其回落的状况
验证了 JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系。
[并发编程] - 操作系统底层工作原理 中 【CPU运行安全等级】部分中说明了从用户态切换到内核态实际上是一个非常重型的操作。
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率 ,可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。
比较常见的几个类的关系如下
Executor接口定义了唯一的接口方法
void execute(Runnable command);
Executor下有一个重要子接口ExecutorService,其中定义了线程池的具体行为
ExecutorService extends Executor
ThreadPoolExecutor 继承 AbstractExecutorService ,而 AbstractExecutorService 实现了ExecutorService 接口。
在Java中,一个int占据32位, 使用了Integer类型来保存,高3位保存runState,低29位保存workerCount
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 用多少二进制位表示线程数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程最大数量 COUNT_BITS 就是29,CAPACITY就是1左移29位减1(29个1) 约5亿
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
先看下这个ctl , 通过 ctlOf(RUNNING, 0) 可以知道 ctlOf这个方法中包含的两个参数信息 : 线程池的运行状态 runState + 线程池中有效线程的数量 workerCount 。
COUNT_BITS : 29
// 高三位表示 线程池状态
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
RUNNING 表示线程池处于 运行状态,COUNT_BITS 是 29,因此这个位运算就表示 -1 左移 29 位。
-1 如用 2 进制表示
获取 -1 的正数,也就是 1 的二进制: 0000000…00000 1 (前面 31 位 0)
对上一步进行取反, 1111111111…1111 0 (前面 31 位 1)
对上一步 +1 操作, 111111111…1111 (32 位 1)
因此 - 1 左移 29 位, 就得到了 111 0000…00000 ( 29个 0) 。 高三位 111 表示 RUNNING 状态
同理
// 高三位为 000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 高三位为 001
private static final int STOP = 1 << COUNT_BITS;
// 高三位为 010
private static final int TIDYING = 2 << COUNT_BITS;
// 高三位为 011
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
// 获取运行状态
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; }
RUNNING
线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
SHUTDOWN
线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN
STOP
线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP
TIDYING
当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
线程池彻底终止,就变成TERMINATED状态。
线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING > TERMINATED。
进入TERMINATED的条件如下: