Java线程池ThreadPoolExecutor-终止线程池原理

2023-10-30

一、终止线程池主要有两个方法:shutdown() 和 shutdownNow()。

shutdown()后线程池将变成shutdown状态,此时不接收新任务,但会处理完正在运行的 和 在阻塞队列中等待处理的任务。
shutdownNow()后线程池将变成stop状态,此时不接收新任务,不再处理在阻塞队列中等待的任务,还会尝试中断正在处理中的工作线程。

二、shutdown() – 温柔的终止线程池

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); //上锁
     
    try {
        //判断调用者是否有权限shutdown线程池
        checkShutdownAccess();
         
        //CAS+循环设置线程池状态为shutdown
        advanceRunState(SHUTDOWN);
         
        //中断所有空闲线程
        interruptIdleWorkers();
         
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } 
    finally {
        mainLock.unlock(); //解锁
    }
     
    //尝试终止线程池
    tryTerminate();
}

1、上锁,mainLock是线程池的主锁,是可重入锁,当要操作workers set这个保持线程的HashSet时,需要先获取mainLock,还有当要处理largestPoolSize、completedTaskCount这类统计数据时需要先获取mainLock

2、判断调用者是否有权限shutdown线程池

3、使用CAS操作将线程池状态设置为shutdown,shutdown之后将不再接收新任务

4、中断所有空闲线程 interruptIdleWorkers()

5、onShutdown(),ScheduledThreadPoolExecutor中实现了这个方法,可以在shutdown()时做一些处理

6、解锁

7、尝试终止线程池 tryTerminate()

可以看到shutdown()方法最重要的几个步骤是:更新线程池状态为shutdown、中断所有空闲线程、tryTerminated()尝试终止线程池
那么,什么是空闲线程?interruptIdleWorkers() 是怎么中断空闲线程的?

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); //上锁
    try {
        for (Worker w : workers) { 
            Thread t = w.thread;
             
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock(); //解锁
    }

interruptIdleWorkers() 首先会获取mainLock锁,因为要迭代workers set,在中断每个worker前,需要做两个判断:

1、线程是否已经被中断,是就什么都不做

2、worker.tryLock() 是否成功

第二个判断比较重要,因为Worker类除了实现了可执行的Runnable,也继承了AQS,本身也是一把锁,具体可见 ThreadPoolExecutor内部类Worker解析

tryLock()调用了Worker自身实现的tryAcquire()方法,这也是AQS规定子类需要实现的尝试获取锁的方法

protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) { 
        setExclusiveOwnerThread(Thread.currentThread()); 
        return true;
    }
    return false;
}

tryAcquire()先尝试将AQS的state从0–>1,返回true代表上锁成功,并设置当前线程为锁的拥有者

可以看到compareAndSetState(0, 1)只尝试了一次获取锁,且不是每次state+1,而是0–>1,说明锁不是可重入的

但是为什么要worker.tryLock()获取worker的锁呢?

这就是Woker类存在的价值之一,控制线程中断

在runWorker()方法中每次获取到task,task.run()之前都需要worker.lock()上锁,运行结束后解锁,即正在运行任务的工作线程都是上了worker锁的
在这里插入图片描述
在interruptIdleWorkers()中断之前需要先tryLock()获取worker锁,意味着正在运行的worker不能中断,因为worker.tryLock()失败,且锁是不可重入的

故shutdown()只有对能获取到worker锁的空闲线程(正在从workQueue中getTask(),此时worker没有加锁)发送中断信号

由此可以将worker划分为:
1、空闲worker:正在从workQueue阻塞队列中获取任务的worker
2、运行中worker:正在task.run()执行任务的worker

正阻塞在getTask()获取任务的worker在被中断后,会抛出InterruptedException,不再阻塞获取任务

捕获中断异常后,将继续循环到getTask()最开始的判断线程池状态的逻辑,当线程池是shutdown状态,且workQueue.isEmpty时,return null,进行worker线程退出逻辑

某些情况下,interruptIdleWorkers()时多个worker正在运行,不会对其发出中断信号,假设此时workQueue也不为空

那么当多个worker运行结束后,会到workQueue阻塞获取任务,获取到的执行任务,没获取到的,如果还是核心线程,会一直workQueue.take()阻塞住,线程无法终止,因为workQueue已经空了,且shutdown后不会接收新任务了

这就需要在shutdown()后,还可以发出中断信号

Doug Lea大神巧妙的在所有可能导致线程池产终止的地方安插了tryTerminated()尝试线程池终止的逻辑,并在其中判断如果线程池已经进入终止流程,没有任务等待执行了,但线程池还有线程,中断唤醒一个空闲线程。
shutdown()的最后也调用了tryTerminated()方法,下面看看这个方法的逻辑:

final void tryTerminate() {
    //这个for循环主要是和进入关闭线程池操作的CAS判断结合使用的
    for (;;) {
        int c = ctl.get();
         
        /**
         * 线程池是否需要终止
         * 如果以下3中情况任一为true,return,不进行终止
         * 1、还在运行状态
         * 2、状态是TIDYING、或 TERMINATED,已经终止过了
         * 3、SHUTDOWN 且 workQueue不为空
         */
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
         
        /**
         * 只有shutdown状态 且 workQueue为空,或者 stop状态能执行到这一步
         * 如果此时线程池还有线程(正在运行任务,正在等待任务)
         * 中断唤醒一个正在等任务的空闲worker
         * 唤醒后再次判断线程池状态,会return null,进入processWorkerExit()流程
         */
        if (workerCountOf(c) != 0) { // Eligible to terminate 资格终止
            interruptIdleWorkers(ONLY_ONE); //中断workers集合中的空闲任务,参数为true,只中断一个
            return;
        }
 
        /**
         * 如果状态是SHUTDOWN,workQueue也为空了,正在运行的worker也没有了,开始terminated
         */
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //CAS:将线程池的ctl变成TIDYING(所有的任务被终止,workCount为0,为此状态时将会调用terminated()方法),期间ctl有变化就会失败,会再次for循环
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated(); //需子类实现
                } 
                finally {
                    ctl.set(ctlOf(TERMINATED, 0)); //将线程池的ctl变成TERMINATED
                    termination.signalAll(); //唤醒调用了 等待线程池终止的线程 awaitTermination() 
                }
                return;
            }
        }
        finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
        // 如果上面的CAS判断false,再次循环
    }
}

tryTerminate() 执行流程:

1、判断线程池是否需要进入终止流程(只有当shutdown状态+workQueue.isEmpty 或 stop状态,才需要)

2、判断线程池中是否还有线程,有则 interruptIdleWorkers(ONLY_ONE) 尝试中断一个空闲线程(正是这个逻辑可以再次发出中断信号,中断阻塞在获取任务的线程)

3、如果状态是SHUTDOWN,workQueue也为空了,正在运行的worker也没有了,开始terminated 会先上锁,将线程池置为tidying状态,之后调用需子类实现的 terminated(),最后线程池置为terminated状态,并唤醒所有等待线程池终止这个Condition的线程

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java线程池ThreadPoolExecutor-终止线程池原理 的相关文章

  • 使用 JDBC 获取 Oracle 11g 的最后插入 ID

    我是使用 Oracle 的新手 所以我将放弃之前已经回答过的内容这个问题 https stackoverflow com questions 3131064 get id of last inserted record in oracle
  • 获取文件的锁

    我想在对特定文件开始 threo read 时获取文件上的锁定 以便其他应用程序无法读取已锁定的文件并希望在线程终止时释放锁定文件 您可以获得一个FileLock https docs oracle com javase 8 docs ap
  • 带有 Android 支持库 v7 的 Maven Android 插件

    我使用 maven android plugin 构建我的 android 应用程序 它依赖于 android 支持库 v4 和 v7 由于我没有找到如何从developer android com下载整个sdk 因此我无法使用maven
  • Java 7 默认语言环境

    我刚刚安装了 jre7 我很惊讶地发现我的默认区域设置现在是 en US 对于jre6 它是de CH 与jre7有什么不同 默认区域设置不再是操作系统之一吗 顺便说一句 我使用的是Windows7 谢谢你的回答 编辑 我已经看到了语言环境
  • Android 中的列表(特别是 RecyclerView 和 CardView)如何工作

    请原谅我问这个问题 但我是 Android 开发新手 尽管我正在尝试了解developer android com 网站上的基础知识 但大多数示例 即使他们说它们是为 Android Studio 构建的 尚未设置为使用 Gradle 因此
  • 当路径的点超出视野时,Android Canvas 不会绘制路径

    我在绘制路径时遇到了 Android Canvas 的一些问题 我的情况是 我有一个相对布局工作 如地图视图 不使用 google api 或类似的东西 我必须在该视图上绘制一条路径 canvas drawPath polyPath bor
  • 如何将jscrollpane添加到jframe?

    我有以下源代码 有人可以给我建议如何将 jscrollpane 添加到 jframe 上吗 我尝试了几次将其添加到 jframe 但没有任何进展 它甚至没有显示 public class Form3 JFrame jframe new JF
  • 将SQL数据引入jquery availabletag

    我正在尝试制作自动完成文本框 但如何将 SQL 数据包含到 jquery 可用标记并循环它 我无法根据以下代码执行该功能 任何帮助 将不胜感激 谢谢 这是我的预期输出 预期结果演示 http jsfiddle net VvETA 71 jq
  • 文本在指定长度后分割,但不要使用 grails 打断单词

    我有一个长字符串 需要将其解析为长度不超过 50 个字符的字符串数组 对我来说 棘手的部分是确保正则表达式找到 50 个字符之前的最后一个空格 以便在字符串之间进行彻底的分隔 因为我不希望单词被切断 public List
  • 如何在java Spring Boot中实现通用服务类?

    我有许多具有重复代码的服务 我想知道如何实现通用服务 以便我的所有服务都可以扩展它 服务接口示例 重复代码 Service public interface IUserService List
  • 如何检测图像是否像素化

    之前有人在 SO 上提出过这样的问题 在Python中检测像素化图像 https stackoverflow com questions 12942365 detecting a pixelated image in python还有关于q
  • Java中的断点和逐步调试?

    抱歉我的问题名称很奇怪 我不知道如何寻找这个 因为我不知道这些东西是如何称呼的 Visual Studio 中至少有一个功能 您可以单击代码左侧并设置一个大红点的起点 然后运行程序 您可以通过按 f8 或 f5 实际上是不同的 f 来跟踪步
  • 在 MongoDB 和 Apache Solr 之间同步数据的简单方法

    我最近开始使用 MongoDB 和 Apache Solr 我使用 MongoDB 作为数据存储 并且希望 Apache Solr 为我的数据创建索引 以实现应用程序中的搜索功能 经过一些研究 我发现 基本上有两种方法可以在 MongoDB
  • org/codehaus/plexus/archiver/jar/JarArchiver(不支持的major.minor版本49.0)-Maven构建错误

    下午大家 我在尝试构建项目时收到上述错误 我很确定这与使用 Java 1 6 编译的 Maven 最新更新有关 而我们尝试构建的项目是 1 4 项目 在此之前的插件工作没有问题 因此我将以下内容添加到 POM xml 文件中以尝试强制使用现
  • 虽然我的类已加载,但 Class.forName 抛出 ClassNotFoundException

    代码如下 它的作用是加载我放在主目录中的 jar 文件中的所有类 import java io File import java util jar JarFile import java util jar JarEntry import j
  • 当 minifyEnabled 为 true 时 Android 应用程序崩溃

    我正在使用多模块应用程序 并且该应用程序崩溃时minifyEnabled true in the installed模块的build gradle 以下是从游戏控制台检索到的反混淆堆栈跟踪 FATAL EXCEPTION Controlle
  • IntelliJ 组织导入

    IntelliJ 是否具有类似于 Eclipse 中的组织导入功能 我拥有的是一个 Java 文件 其中多个类缺少导入 例子 package com test public class Foo public Map map public J
  • 如何从 Ant 启动聚合 jetty-server JAR?

    背景 免责声明 I have veryJava 经验很少 我们之前在 Ant 构建期间使用了 Jetty 6 的包装版本来处理按需静态内容 JS CSS 图像 HTML 因此我们可以使用 PhantomJS 针对 HTTP 托管环境运行单元
  • 源值 1.5 的错误已过时,将在未来版本中删除

    我使用 scala maven plugin 来编译包含 scala 和 java 代码的项目 我已经将源和目标设置为1 7 但不知道为什么maven仍然使用1 5 这是我在 pom xml 中的插件
  • 如何使用通配符模拟泛型方法的行为

    我正在使用 EasyMock 3 2 我想基于 Spring Security 为我的部分安全系统编写一个测试 我想嘲笑Authentication http docs spring io autorepo docs spring secu

随机推荐

  • L1-095 分寝室PTA

    学校新建了宿舍楼 共有 n 间寝室 等待分配的学生中 有女生 n0 位 男生 n1 位 所有待分配的学生都必须分到一间寝室 所有的寝室都要分出去 最后不能有寝室留空 现请你写程序完成寝室的自动分配 分配规则如下 男女生不能混住 不允许单人住
  • git删除本地分支、远程分支、创建分支

    git删除本地分支 远程分支 创建分支 git创建本地分支 git checkout b 分支名 git删除本地分支 git branch D 本地分支名 git删除远程分支 git push origin delete 远程分支名 git
  • 用生日攻击方法求解离散对数问题(C语言实现)-大三密码学实验

    实验原理 生日攻击 输入为生成元a的阶p 1和元b 输出为离散对数 设置两个长度为p的列表 1 列表1包含 通过随机选取p个k得到 2 列表2包含 通过随机选取p个l得到 则在两个列表中很有可能出现重复的项 即 因此 那么就是要找的离散对数
  • Map集合的Key比较

    类 Object 是类层次结构的根类 每个类都使用 Object 作为超类 所有对象 包括数组 都实现这个类的方法 在Object类中有两个重要的方法 hashCode 和equals Object obj 在我们使用集合容器的时候我们需要
  • allegro17.4 中规则的导入与导出

    在使用allegro时 经常会遇到多个项目的规则一致 因此进行规则的复用很有必要 1 打开已经设置好规则的PCB文件 执行菜单setup constraints constraint manager 进入菜单管理器 2 然后点击file e
  • Elasticsearch:RestClient+SearchSourceBuilder使用案例

    1 前言 RestClient是较低层的API 这里使用基于其封装的高层API 即RestHighLevelClient 需要添加的依赖如下
  • Altium AD20更改原理图背景颜色

    AD20更改原理图背景颜色 点击右下角 Panels 选择 Properties Sheet Color 即为背景颜色 Sheet Border 为 页面信息栏 及 最外层边框 的颜色 AD默认的淡黄色背景颜色为 FFFCF8 更改回默认可
  • axios 或 fetch 如何实现对发出的请求的终止?

    终止 HTTP 请求是一个重要的功能 特别是在需要优化性能 避免不必要的请求或在某些事件发生时 例如用户点击取消 中断正在进行的请求时 以下是如何使用 axios 和 fetch 实现请求终止的方法 1 axios axios 使用了 Ca
  • 微信小程序基础使用-请求数据并渲染

    小程序基本使用 请求数据并渲染 小程序模板语法 数据绑定 在js中定义数据 Page data isOpen true message hello world 小程序的data是一个对象 不同于vue的data是一个函数 在模块中获取使用数
  • Bash基本命令用法

    man 查看命令帮助信息 echo 在终端输出字符 reboot 重启 poweroff 关机 ps 查看系统中的进程状态 top 动态的监视进程活动与系统负载等信息 pidof 查询某个指定服务进程的pid值 kill 终止某个指定pid
  • 3DsMax次级菜单无法选中---简单方法

    在桌面新建文本文档 将下列代码赋值到文本文档中 Ctrl S保存代码 然后改扩展名为 reg 最后双击导入就完美解决了 Win10系统代码 Windows Registry Editor Version 5 00 HKEY CURRENT
  • mybatis查询当年的数据、查询当天的数据、查询当年每个月的数据

    mybatis查询当年 当天的数据 查询当年每个月的数据 最近遇到公司的需求 查询一个表里面当年的数据和当年每个月的数据 测试数据表如下 很显然我们要对time字段进行操作 查询当天的数据 我们可以在where后面这么写 AND TO DA
  • windows建立隐藏用户及查找隐藏用户

    建立隐藏用户 1 cmd gt net user 用户名 密码 add hint cmd gt net localgroup administrators 用户名 add 将用户添加进管理员组 2 cmd gt regedit 路径 HKE
  • ty计算机网络背诵简答题满分冲刺版!

    ty计算机网络背诵简答题满分冲刺版 ty 2023 名词解释 记忆中文名称即可 缩写 全称 解释 DNS 域名系统 用于将域名转换为IP地址的分布式数据库系统 URL 统一资源定位符 用于标识互联网上资源位置的地址 IGP 内部网关协议 用
  • 什么时候要有拷贝构造函数?

    1 构造对象 时 用已实例化的对象进行构造 int main Student a 10 2 Student b a 拷贝构造 并且要特别注意指针数据成员的赋值 避免出现野指针的情况 2 返回临时对象 而不是返回对象的引用 rmb opera
  • 如何使用代理IP池进行爬虫工作

    要使用代理IP池进行爬虫 需要先将代理IP池集成到爬虫代码中 并在需要访问目标网站时随机选取代理IP进行访问 简单的步骤如下 1 导入代理IP池模块 将代理IP池的模块集成到你的爬虫代码中 通常情况下 代理IP池提供了一些API接口 可以通
  • Android中常用的adb shell命令

    注意事项 这里写的命令 网页可能会重新编辑格式 比如我写了两个减号 发布后变成了一个减号 如果这里的命令不能正确执行 请手动输入命令 切记切换英文状态 android 常用shell命令记录下来备忘 n 在shell命令中的作用是换行 而
  • 8.Spring EL与ExpressionParser

    Spring EL与ExpressionParser 文章目录 Spring EL与ExpressionParser 介绍 使用SpEL来计算评估文字字符串表达式 使用SpEL来计算评估 bean 属性 item name 介绍 Sprin
  • 原生js实现水印背景

    参考文档 https juejin cn post 6844904095749242888 1 通过canvas生成水印图片 2 把图片作为div的背景让其平铺 const setWatermark gt let canvas docume
  • Java线程池ThreadPoolExecutor-终止线程池原理

    一 终止线程池主要有两个方法 shutdown 和 shutdownNow shutdown 后线程池将变成shutdown状态 此时不接收新任务 但会处理完正在运行的 和 在阻塞队列中等待处理的任务 shutdownNow 后线程池将变成