Java 深入理解线程池
一、Java 中的线程池
1. 线程池状态
ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量
// runState is stored in the high-order bits
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;
private static final int TERMINATED = 3 << COUNT_BITS;
1
2
3
4
5
6
在这里插入图片描述
线程池状态和线程池中线程的数量由一个原子整型ctl来共同表示
使用一个数来表示两个值的主要原因是:可以通过一次CAS同时更改两个属性的值
// 原子整数,前3位保存了线程池的状态,剩余位保存的是线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//去掉前三位保存线程状态的位数,剩下的用于保存线程数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// 2^COUNT_BITS次方,表示可以保存的最大线程数
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
1
2
3
4
5
6
7
8
获取线程池状态、线程数量以及合并两个值的操作
// Packing and unpacking ctl
// 传入 ctl 值 获取运行状态 该操作会让除高3位以外的数全部变为0
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 传入 ctl 值 获取运行线程数 该操作会让高3位为0
private static int workerCountOf(int c) { return c & CAPACITY; }
// 传入 rs 运行状态 wc 线程数量 计算ctl新值
private static int ctlOf(int rs, int wc) { return rs | wc; }
1
2
3
4
5
6
7
8
9
10
2. 线程池主要属性参数
//阻塞队列,用于存放来不及被核心线程执行的任务
private final BlockingQueue<Runnable> workQueue;
// 全局锁,解决创建销毁线程等线程安全问题
private final ReentrantLock mainLock = new ReentrantLock();
// 用于存放核心线程的容器,只有当持有锁时才能够获取其中的元素
private final HashSet<Worker> workers = new HashSet<Worker>();
//线程工厂,给线程取名字
private volatile ThreadFactory threadFactory;
// 拒绝执行处理器 处理拒绝策略
private volatile RejectedExecutionHandler handler;
// 救急线程(或者核心线程)空闲时的最大生存时间
private volatile long keepAliveTime;
// 核心线程数
private volatile int corePoolSize;
// 最大线程数
// 最大线程数 - 核心线程数 = 九级线程数
private volatile int maximumPoolSize;
// 默认拒绝策略
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
corePoolSize : (核心线程数量),如果调用了线程池的 prestartAllCoreThreads( ) 方法,线程池会提前创建并启动所有基本线程,否则是懒惰创建
workQueue:用于保存等待执行的任务的阻塞队列。可以选择以下几个具体实现
① ArrayBlockingQueue:是一个基于数组的有界阻塞队列,按FIFO(先进先出原则)排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。
② LinkedBlockingQuene:基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。吞吐量通常要高于ArrayBlockingQueue 。静态工厂方法 Executors.newFixedThreadPool( ) 使用了该队列
③ SynchronousQuene 是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene ,静态工厂方法 Executors.newCachedThreadPool() 用的是此队列
④ PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
maximumPoolSize:线程池最大线程数量,包括了核心线程数量和救急线程数量
threadFactory:线程工厂,可以给线程设置名字等
handler:拒绝执行处理器 处理拒绝策略 在处理过程中具体讲解
keepAliveTime:线程活动保持时间,线程池工作线程空闲后,保持存活的时间,所以,如果任务很多,并且每个任务执行时间很多,可以调大存活时间,提高线程利用率
unit:空闲线程存活时间单位
3. 线程池的实现原理
3.1 ThreadPoolExecutor 线程池主要处理流程
在这里插入图片描述
在这里插入图片描述
使用者 发布任务
如果当前运行的线程少于 核心线程数(corePoolSize),则创建新线程来执行任务(这一步需要获得全局锁,不然会引发线程安全问题)
如果运行的线程等于或者大于corePoolSize 则将任务加入阻塞队列(BlockQueue)
如果BlockQueue 已满,无法将任务加入队列,则创建新线程来处理任务(这同样需要获得全局锁)
此处就用到救急线程,其数量就是最大线程数减去核心线程数的数量
如果创建新线程使当前运行的线程超出maximumPoolSize 任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法
拒绝策略 jdk 提供了 4 种实现
在这里插入图片描述
AbortPolicy:让调用者抛出 RejectedExecutionException 异常,这是默认策略
CallerRunsPolicy:让调用者运行任务
DiscardPolicy:放弃本次任务
DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之
————————————————
版权声明:本文为CSDN博主「A.iguodala」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48922154/article/details/115681974
- 点赞
- 收藏
- 关注作者
评论(0)