解析ThreadPoolExecutor类是如何保证线程池正确运行的
摘要:对于线程池的核心类ThreadPoolExecutor来说,有哪些重要的属性和内部类为线程池的正确运行提供重要的保障呢?
本文分享自华为云社区《【高并发】通过源码深度解析ThreadPoolExecutor类是如何保证线程池正确运行的》,作者: 冰 河 。
对于线程池的核心类ThreadPoolExecutor来说,有哪些重要的属性和内部类为线程池的正确运行提供重要的保障呢?今天我们就一起来深入探讨下这些问题!!
ThreadPoolExecutor类中的重要属性
在ThreadPoolExecutor类中,存在几个非常重要的属性和方法,接下来,我们就介绍下这些重要的属性和方法。
ctl相关的属性
AtomicInteger类型的常量ctl是贯穿线程池整个生命周期的重要属性,它是一个原子类对象,主要用来保存线程的数量和线程池的状态,我们看下与这个属性相关的代码如下所示。
//主要用来保存线程数量和线程池的状态,高3位保存线程状态,低29位保存线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程池中线程的数量的位数(32-3)
private static final int COUNT_BITS = Integer.SIZE - 3;
//表示线程池中的最大线程数量
//将数字1的二进制值向右移29位,再减去1
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;
private static final int TERMINATED = 3 << COUNT_BITS;
//获取线程状态
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; }
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
对于线程池的各状态说明如下所示。
- RUNNING:运行状态,能接收新提交的任务,并且也能处理阻塞队列中的任务
- SHUTDOWN: 关闭状态,不能再接收新提交的任务,但是可以处理阻塞队列中已经保存的任务,当线程池处于RUNNING状态时,调用shutdown()方法会使线程池进入该状态
- STOP: 不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于RUNNING或SHUTDOWN状态,调用shutdownNow()方法,会使线程池进入该状态
- TIDYING: 如果所有的任务都已经终止,有效线程数为0(阻塞队列为空,线程池中的工作线程数量为0),线程池就会进入该状态。
- TERMINATED: 处于TIDYING状态的线程池调用terminated ()方法,会使用线程池进入该状态
也可以按照ThreadPoolExecutor类的,将线程池的各状态之间的转化总结成如下图所示。

- RUNNING -> SHUTDOWN:显式调用shutdown()方法, 或者隐式调用了finalize()方法
- (RUNNING or SHUTDOWN) -> STOP:显式调用shutdownNow()方法
- SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候
- STOP -> TIDYING:当线程池为空的时候
- TIDYING -> TERMINATED:当 terminated() hook 方法执行完成时候
其他重要属性
除了ctl相关的属性外,ThreadPoolExecutor类中其他一些重要的属性如下所示。
//用于存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//可重入锁
private final ReentrantLock mainLock = new ReentrantLock();
//存放线程池中线程的集合,访问这个集合时,必须获得mainLock锁
private final HashSet<Worker> workers = new HashSet<Worker>();
//在锁内部阻塞等待条件完成
private final Condition termination = mainLock.newCondition();
//线程工厂,以此来创建新线程
private volatile ThreadFactory threadFactory;
//拒绝策略
private volatile RejectedExecutionHandler handler;
//默认的拒绝策略
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
ThreadPoolExecutor类中的重要内部类
在ThreadPoolExecutor类中存在对于线程池的执行至关重要的内部类,Worker内部类和拒绝策略内部类。接下来,我们分别看这些内部类。
Worker内部类
Worker类从源代码上来看,实现了Runnable接口,说明其本质上是一个用来执行任务的线程,接下来,我们看下Worker类的源代码,如下所示。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
private static final long serialVersionUID = 6138294804551838833L;
//真正执行任务的线程
final Thread thread;
//第一个Runnable任务,如果在创建线程时指定了需要执行的第一个任务
//则第一个任务会存放在此变量中,此变量也可以为null
//如果为null,则线程启动后,通过getTask方法到BlockingQueue队列中获取任务
Runnable firstTask;
//用于存放此线程完全的任务数,注意:使用了volatile关键字
volatile long completedTasks;
//Worker类唯一的构造放大,传递的firstTask可以为null
Worker(Runnable firstTask) {
//防止在调用runWorker之前被中断
setState(-1);
this.firstTask = firstTask;
//使用ThreadFactory 来创建一个新的执行任务的线程
this.thread = getThreadFactory().newThread(this);
}
//调用外部ThreadPoolExecutor类的runWorker方法执行任务
public void run() {
runWorker(this);
}
//是否获取到锁
//state=0表示锁未被获取
//state=1表示锁被获取
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
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(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
在Worker类的构造方法中,可以看出,首先将同步状态state设置为-1,设置为-1是为了防止runWorker方法运行之前被中断。这是因为如果其他线程调用线程池的shutdownNow()方法时,如果Worker类中的state状态的值大于0,则会中断线程,如果state状态的值为-1,则不会中断线程。
Worker类实现了Runnable接口,需要重写run方法,而Worker的run方法本质上调用的是ThreadPoolExecutor类的runWorker方法,在runWorker方法中,会首先调用unlock方法,该方法会将state置为0,所以这个时候调用shutDownNow方法就会中断当前线程,而这个时候已经进入了runWork方法,就不会在还没有执行runWorker方法的时候就中断线程。
注意:大家需要重点理解Worker类的实现。
拒绝策略内部类
在线程池中,如果workQueue阻塞队列满了,并且没有空闲的线程池,此时,继续提交任务,需要采取一种策略来处理这个任务。而线程池总共提供了四种策略,如下所示。
- 直接抛出异常,这也是默认的策略。实现类为AbortPolicy。
- 用调用者所在的线程来执行任务。实现类为CallerRunsPolicy。
- 丢弃队列中最靠前的任务并执行当前任务。实现类为DiscardOldestPolicy。
- 直接丢弃当前任务。实现类为DiscardPolicy。
在ThreadPoolExecutor类中提供了4个内部类来默认实现对应的策略,如下所示。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
我们也可以通过实现RejectedExecutionHandler接口,并重写RejectedExecutionHandler接口的rejectedExecution方法来自定义拒绝策略,在创建线程池时,调用ThreadPoolExecutor的构造方法,传入我们自己写的拒绝策略。
例如,自定义的拒绝策略如下所示。
public class CustomPolicy implements RejectedExecutionHandler {
public CustomPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
System.out.println("使用调用者所在的线程来执行任务")
r.run();
}
}
}
使用自定义拒绝策略创建线程池。
new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
Executors.defaultThreadFactory(),
解析ThreadPoolExecutor类是如何保证线程池正确运行的的更多相关文章
- 【高并发】通过源码深度解析ThreadPoolExecutor类是如何保证线程池正确运行的
大家好,我是冰河~~ 对于线程池的核心类ThreadPoolExecutor来说,有哪些重要的属性和内部类为线程池的正确运行提供重要的保障呢? ThreadPoolExecutor类中的重要属性 在T ...
- Netty 源码解析(五): Netty 的线程池分析
今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
- ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别
工作中多处接触到了ThreadPoolExecutor.趁着现在还算空,学习总结一下. 前记: jdk官方文档(javadoc)是学习的最好,最权威的参考. 文章分上中下.上篇中主要介绍ThreadP ...
- 并发编程常用工具类(二) SymaPhore实现线程池
1.symaPhore简介 symaphore(信号量)用来控制同时访问某个资源的线程数量,一般用在并发流量控制.个人对它的理解相当于是接待室每次只能接待固定数量的人,当达到最高接待数的时候,其他人就 ...
- Java 数量为5的线程池同时运行5个窗口买票,每隔一秒钟卖一张票
/** * 1.创建线程数量为5的线程池 * 2.同时运行5个买票窗口 * 3.总票数为100,每隔一秒钟卖一张票 * @author Administrator * */ public class ...
- 线程池 多线程运行结束后 如何关闭? ExecutorService的正确关闭方法
前言 最近在使用ExecutorService的时候,对于与ExecutorService相关的概念有些迷糊, 加上本身ExecutorService内部的有些方法名在取名上也容易让使用者误解,导致 ...
- java多线程系类:JUC线程池:02之线程池原理(一)
在上一章"Java多线程系列--"JUC线程池"01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我 ...
- juc线程池原理(二):ThreadPoolExecutor的成员变量介绍
概要 线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析ThreadPoolExecutor类,来了解线程池的原理. ThreadPoolExecutor数据结构 Thread ...
- Android 性能优化(16)线程优化:Creating a Manager for Multiple Threads 如何创建一个线程池管理类
Creating a Manager for Multiple Threads 1.You should also read Processes and Threads The previous le ...
随机推荐
- 【LeetCode】404. 左叶子之和
404. 左叶子之和 知识点:二叉树 题目描述 计算给定二叉树的所有左叶子之和.. 示例 3 / \ 9 20 / \ 15 7 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 解 ...
- 全站 HTTPS 就一定安全了吗?
随着网络技术手段不断地更新迭代,互联网安全对于企业和个人的重要性都越来越高.因此越来越多的服务商都开始偏向为用户提供更安全的在线内容访问. 中间人攻击 为了保障网站内容安全,诞生了不少加密方式.目前应 ...
- 解决proto文件转换时提示“Note that enum values use C++ scoping rules, meaning that enum values are siblings of their type, not children of it. ”
前言: 想将.proto文件转换成.pb文件时一直报错,一开始以为是文件编码格式的问题,后来将文件改成windows下的utf-8格式后,又出现了新的报错(见下图).百度了很久,才找到解决方法. &q ...
- docker exec 参数详解
Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] Run a command in a running container Options ...
- 初探Node-red结合阿里云数据库,定时显示数据
在最近的项目中,隔壁项目组推荐了一个很好的基于node.js开源IOT框架Node-Red,它的优点很明显,使用可视化编程的方式,实现了大部分需要在物联网领域的技术,而且能很好的对接各个云平台,快捷部 ...
- Samba 远程命令执行漏洞(CVE-2017-7494)
该漏洞影响Samba 3.5.0之后的所有版本,在4.6.4/4.5.10/4.4.14修复了这个漏洞 use exploit/linux/samba/is_known_pipename set rh ...
- Activiti7 结束/终止流程
1. 结束/终止 正在运行的流程实例 思路:跟回退一样的思路一样,直接从当前节点跳到结束节点(EndEvent) /** * 结束任务 * @param taskId 当前任务ID */ publi ...
- Tensorflow2对GPU内存的分配策略
一.问题源起 从以下的异常堆栈可以看到是BLAS程序集初始化失败,可以看到是执行MatMul的时候发生的异常,基本可以断定可能数据集太大导致memory不够用了. 2021-08-10 16:38:0 ...
- Java数组06——冒泡排序
冒泡排序 例子: package array; import java.util.Arrays; public class ArrayDemon08 { public static ...
- CVE-2021-3156 复现
测试环境 OS: Ubuntu 18.04.5 LTS GCC: gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04) Make: GNU Make 4.1 ...