ThreadPoolExecutor线程复用与超时销毁的原理

2023-05-16

前言

线程池基本上是每个业务都接触的,然而线程池是怎么复用线程,线程是怎么自动超时回收♻️,core核心线程为什么不回收,一直没有过多关注,最近有疑问这些事怎么实现的,偶有所得。

1. 原理分析

其实很早之前,笔者就分析了ThreadPoolExecutor的源码,只是并没有针对线程复用,回收的角度去分析原理,单纯的分析了源码主逻辑。

(65条消息) JDK8线程池-ThreadPoolExecutor源码解析_fenglllle的博客-CSDN博客https://blog.csdn.net/fenglllle/article/details/82790242?spm=1001.2014.3001.5502

关键还是runWorker,其他核心代码见上面的链接。

1.1 线程池线程复用的秘密

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

这里 while (task != null || (task = getTask()) != null) 

可以看出只要能获取task,那么循环就不会结束,这就是线程池的线程复用的秘密,就是让线程在有活干的时候,拼命干活,不休息,😝

那么线程池是怎么回收的呢,其实也很简单,拿不到task就会没活干,就释放了,线程自然运行结束销毁,就是活干完了。

那么线程池如何实现延时回收多余core的线程,又是如何定时回收的呢,如何保证core线程不回收

1.2 线程池线程回收的秘密

关键还是在获取task的代码

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            //是否core线程超时回收或者工作线程超过core
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                //CAS技术
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    //队列按照设置的超时,获取任务
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    //阻塞,直到拿到任务
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

可以看到allowCoreThreadTimeOut默认是不超时的,当work线程<= core时,就会阻塞当前线程workQueue.take();


boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;  

 此时就是只剩下核心线程了,默认是不超时回收的,就阻塞在队列这里。

如果>core,那么就超时等待队列获取任务,如果有任务就继续干活😄,否则就标记timeOut为true,在下次循环CAS技术减掉work线程数直接返回,这就是超时回收的原理,没任务了,线程自己结束生命周期了。

验证

上面仅仅是理论分析,实际证明线程复用,超时,核心线程的现象

public class ThreadMain {

    private static final AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
                5,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(5), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("----------haha-mime---------"+atomicInteger.getAndIncrement());
                return t;
            }
        });

        int i=0;
        while (i < 200) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
            i++;
        }

        Thread.sleep(120000);
        System.out.println("=================== end =================");
    }
}

执行后打印,可以说明只创建了5个线程,也可以断点运行,实际启动5个线程,而且启动了5个线程,并且执行了自定义拒绝策略(我直接修改的源码,使用jdk源码需要注入自定义拒绝策略的对象)。

等待一段时间,线程dump,可以看到,线程正在等待超时时间获取任务

 

再等一段时间,dump线程,剩下core线程正被队列阻塞

总结

线程池本质就是很多线程的管理,线程还是遵循生命周期,只是统计了数量,加入了队列,通过队列任务管理来达到复用线程的能力,使用阻塞来操作线程的生命周期,来达到线程的超时销毁、核心线程一直运行的目的。

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

ThreadPoolExecutor线程复用与超时销毁的原理 的相关文章

  • git 添加源地址和查看源地址

    git init 添加本地文件关联 git add commit 提交 git commit m 34 first commit 34 关联仓库 git remote add origin http git sfc com yuxang w
  • Python爬取淘宝商品数据,价值千元的爬虫外包项目

    前言 本文的文字及图片来源于网络 仅供学习 交流使用 不具有任何商业用途 如有问题请及时联系我们以作处理 PS xff1a 如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 完整代码可以点击下方链接获取 python免费学习资
  • AndroidStudio安装kotlin插件

    转载请注明出处 xff1a http blog csdn net feibendexiaoma article details 72625846 前言 2017 Google I O大会宣布将Kotlin语言作为安卓开发的一级编程语言 xf
  • Ubuntu20.04/Ubuntu22.04 配置VScode+Opencv+cmake(C++)

    Ubuntu20 04 Ubuntu22 04 配置VScode 43 Opencv 43 cmake xff08 C 43 43 xff09 下面介绍Ubuntu20 04下安装opencv xff0c 当然Ubuntu22 04也适用
  • Ubuntu-解决包依赖关系

    Ubuntu 解决包依赖关系的办法 安装软件包的时候 xff0c 有时会遇到类似下图的依赖问题 xff0c 无法正常安装 xff0c 下面提供三种方法解决依赖问题 1 可以尝试用下面方法处理依赖问题 xff0c 紧跟前一条安装命令后面输入下
  • Ubuntu 18.04系统进不去了,只有老内核的恢复模式还能进,请大家帮看一下

    Ubuntu 18 04 新内核和恢复模式进不去 xff0c 会报错 老内核也进不去 xff0c 只有老内核的恢复模式能进 xff0c 请大佬帮看一下 virtualbox里安装的是Ubuntu 18 04 1 LTS xff0c 打开id
  • Android Lottie动画的简单使用

    简介 在Android中做动画效果无非是以下几种方法 xff1a 普通动画帧动画属性动画通过改变LayoutParams布局参数来实现动画 现如今在Github上有一个比较火的动画库Lottie xff0c Github上关于Lottie库
  • java运行提示没有 org/slf4j/LoggerFactory 或者 log4j

    问题描述 Exception in thread main java lang NoClassDefFoundError org slf4j LoggerFactory 解决办法 在pom xml里面添加对应的配置 将jar包大包进去 lt
  • Blazor入门100天 : 身份验证和授权 (3) - DB改Sqlite

    目录 建立默认带身份验证 Blazor 程序角色 组件 特性 过程逻辑DB 改 Sqlite将自定义字段添加到用户表脚手架拉取IDS文件 本地化资源freesql 生成实体类 freesql 管理ids数据表初始化 Roles freesq
  • ExecutorService等待线程完成后优雅结束

    1 概述 该ExecutorService框架可以很容易地在处理多线程任务 我们将举例说明我们等待线程完成执行的一些场景 此外 xff0c 我们将展示如何正常关闭ExecutorService并等待已经运行的线程完成其执行 2 Execut
  • linux中出错处理

    linux中 xff0c 在支持多线程的环境中 xff0c 通常每个线程都有属于自己的errno变量 xff0c 是用来表示特定错误的常量 以下是 lt errno h gt 中定义的所有出错errno常量 define EPERM 1 O
  • linux-capabilities

    导航 返回顶部 1 man capabilities 1 1 Capabilities 功能1 2 全面实施功能需要 在内核2 6 24之前 xff0c 仅满足前两个要求 xff1b 从内核2 6 24开始 xff0c 所有这三个要求都得到
  • Arch-base-vs-iso

    Arch base vs iso Arch base vs iso 通常绝大多数的Linux分发版的iso镜像本身 iso文件都有约2Gb上下 都可以直接启动电脑并运行完整的Linux桌面系统 极少数的Linux发行版仅提供命令行界面 xf
  • Arch系统软件列表

    Arch系统软件列表 1 安装统计 2 安装列表 3 安装说明 4 作为依赖项的安装列表 5 更正 mangaro使用减的方式安装系统 开箱即用的豪华版本 xff0c 大部分人需要的都有了 xff0c 同样包括个别用户不需要的 xff0c
  • 服务器如何设置多用户登录?Windows服务器多界面设置方法

    当你在使用服务器时是否有遇到这样一个问题 xff1f 当你正在服务器里进行工作时 xff0c 突然一个小伙伴在没有告知你的情况下进入了服务器里 xff0c 导致你服务器失去连接了 xff0c 这种情况是非常常见的现象 主要原因就是因为服务器
  • 运算符:&和&&的区别

    amp 运算符有两种用法 xff1a 1 按位与 xff1b 2 逻辑与 amp amp 运算符是短路与运算 逻辑与跟短路与的差别是非常巨大的 xff0c 虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true amp
  • python删除txt文本文件第一行数据

    txt文本单次删除第一行 txt文本单次删除第一行 with open 39 test txt 39 mode 61 39 r 39 encoding 61 39 utf 8 39 as f line 61 f readlines 读取文件
  • Android WebView 网页使用本地字体

    要求在网页里面调用android app中assets目录下的某个字体文件 网页加载通常有两种方式 xff1a 1 loadDataWithBaseURL 2 loadUrl 一 loadDataWithBaseURL 网页中直接使用fil
  • Blazor入门100天 : 身份验证和授权 (4) - 自定义字段

    目录 建立默认带身份验证 Blazor 程序角色 组件 特性 过程逻辑DB 改 Sqlite将自定义字段添加到用户表脚手架拉取IDS文件 本地化资源freesql 生成实体类 freesql 管理ids数据表初始化 Roles freesq
  • 若依前后端分离版,图片上传后无法显示问题

    若依前后端分离版 xff0c 部署时通常会采用Nginx做反向代理 访问图片出现404 Not Found问题 若依在文件上传成功后 xff0c 请求访问后台地址会根据profile进行匹配 xff0c 需要自己配置代理 配置方式一 xff

随机推荐