ThreadPoolExecutor部分方法解读

2024-06-06 05:18

本文主要是介绍ThreadPoolExecutor部分方法解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 状态与位移运算、或与非
    • field分析:
    • 方法分析:
  • execute(Runnable command)分析
  • addWorker(Runnable firstTask, boolean core)
  • 使用技巧
  • 代码疑问

状态与位移运算、或与非

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bitsprivate 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;// Packing and unpacking ctlprivate 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; }

ctl:
使用AtomicInteger,目前够用,如果未来会有问题,可以改成AtomicLong,现在来说执行效率更高.
c = ctl.get() < SHUTDOWN 表示在运行中的状态

field分析:

COUNT_BITS是29, 各常量的二进制字节码如下:
CAPACITY: 00011111111111111111111111111111(前3位是0,后29位是1,共32位,约5亿)
RUNNING: 11100000000000000000000000000000(负数,前3位是1,后29位是0,共32位)
SHUTDOWN: 00000000000000000000000000000000(正数)
STOP: 00100000000000000000000000000000(正数)
TIDYING: 01000000000000000000000000000000(正数)
TERMINATED: 01100000000000000000000000000000(正数)
以上可知:

  • 1.CAPACITY和RUNNING是相互取反的关系
  • 2.ctl:是runState和workerCounter的结合体,初始值等于RUNNING和0的或运算的拼接结果
    ctlOf(RUNNING, 0)
  • 3.ctl#get的值,调用workerCountOf方法获取线程数,调用runStateOf方法得到线程池状态.

方法分析:

// 与运算后只有c的后29位有效
private static int workerCountOf(int c)  { return c & CAPACITY; }

中的入参c,即为ctl的值.该方法含义是:获取工作线程数(取后29位),初始值是0

// 取前三位,~表示非运算
private static int runStateOf(int c)     { return c & ~CAPACITY; }

先对CAPACITY进行非运算,变为11100000000000000000000000000000,再进行与运算,取前三位,表示线程的状态

private static int ctlOf(int rs, int wc) { return rs | wc; }

运行状态runState和工作线程数workerCount取或,即二者拼接起来.

execute(Runnable command)分析

  • 1.比较工作线程数与核心线程数corePoolSize,如果小于,则创建线程 并执行任务
        if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}
  • 2.当核心线程都已经创建完成,仍然有任务加入,就丢到队列里面
        if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}

这一步的前提是: 线程池在运行中,队列未满.
如果线程池已经关闭,要移除任务,执行拒绝策略.
如果工作线程已经死了(workerCountOf(recheck) == 0),需要重新创建线程.(这里要怎么理解呢?不是已经创建了核心线程吗,怎么会有工作线程为0的情况?)

addWorker(null, false)这里false表示是非核心线程,加的任务是null,是因为任务在前面已经放到队列里面了:workQueue.offer(command)

addWorker(Runnable firstTask, boolean core)

            if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;

addWorker前先做判断:
如果线程池为RUNNING状态或者为SHUTDOWN状态且此时任务队列仍有任务未执行完时,可以继续调用addWorker添加工作线程,但不能新建任务,即firstTask参数必须为null.否则这里将返回false,即新建工作线程失败。
简单地说,就是如果是shutdown状态,就不能再往队列里面加任务了.但是如果shutdown状态下任务队列还有任务,就还可以创建线程去执行任务.

            for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctlif (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}

如果 工作线程数大于CAPACITY 或者大于corePoolSize或maximumPoolSize(视入参core而定),则返回false.

compareAndIncrementWorkerCount将ctl的值+1, 成功就跳出循环往下执行.否则就重新获取ctl的值,判断是否已经更新,以确定继续进行内部循环(未更新)或者外部循环(已更新)
随后使用可重入锁同步创建worker对象,并启动线程.

如果addWorker返回成功,那么这时候线程已经开始执行相应的任务了;

使用技巧

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池,以减少线程切换带来的性能开销。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

代码疑问

对于runWorker方法,一开始对代码

        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 interruptif ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();// 此处省略部分代码...}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}

中的processWorkerExit部分不解,以为走到这里不就是销毁worker对象了吗?那么岂不是每个任务都要创建worker对象,即创建新线程?
其实不是这样的.里面的while (task != null || (task = getTask()) != null)部分会一直执行下去,如果没有task,代码会阻塞在这里,直到从队列里面获取到task为止.

        try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}

而processWorkerExit方法的执行的条件是上面的while循环结束了.while循环结束的原因是:
线程池SHUTDOWN,或者工作线程数 > corePoolSize,且队列为空,没有task.
这就好理解了:
线程池关闭 --> 销毁全部worker对象,
线程数 > corePoolSize,且队列为空 --> 销毁大于核心线程数的worker对象!



细节代码区分:

在这里插入图片描述
runWorker方法
这2处需要区分一下.
t.start()的t是worker里面的Thread对象,该对象封装的Runnable对象正是worker.所以start方法启动线程后就是执行worker#run方法,即worker#runWorker方法.
task.run()的task就是传入的用户写的任务.

这篇关于ThreadPoolExecutor部分方法解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1035187

相关文章

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

MCU7.keil中build产生的hex文件解读

1.hex文件大致解读 闲来无事,查看了MCU6.用keil新建项目的hex文件 用FlexHex打开 给我的第一印象是:经过软件的解释之后,发现这些数据排列地十分整齐 :02000F0080FE71:03000000020003F8:0C000300787FE4F6D8FD75810702000F3D:00000001FF 把解释后的数据当作十六进制来观察 1.每一行数据

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时