`
snake1987
  • 浏览: 72761 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发学习之二:线程池(五)

    博客分类:
  • java
阅读更多
之前的线程池已实现了基本的功能:运行每一个线程,而且测试了一下,大约速度是ThreadPoolExecutor的1.5倍(当然,这是有充分的理由的,后文会提到)

之后的版本将准备是实现“优雅退出”和优化(非阻塞)空闲线程队列了,这个步骤想了很久,发现了很多的问题(包括准备的实现方法也在这里列一下):
1.初步构思了几个方法
  • void shutDown():该方法将让池不再接受任务,但会将现有的任务全部运行结束后停止
  • List<Runnable> shutDownNow():该方法会interrupt线程,然后收集未调用的任务,并进一步回收已调用的任务(“已调用”不包括运行中的,因为运行中无法人为停止,只包括已下发到线程,但线程尚未执行的),然后将这些任务返回
  • List<Runnable> shutDownAndWait():跟shutDownNow类似,区别是会等待至线程池完全关闭

2.线程及池的状态,一开始很傻气地用了boolean去实现,比如:isShutDown,isRunning等,而这些变量又无法避免地被声明为volatile,我们知道,对volatile的写和读都会比对线程的内部变量的访问占用更多的消耗,与其用多个boolean,不如用一个runState,只需要判断大小就可以了,但对volatile变量的访问只有一次,而判断大小所占用的cpu时间几乎可以忽略不计
这也是一个原则性的问题了,这里记录下来,算是积累把:
表示一个东西的状态(多个状态),尽量还是使用int,如果有同步问题,则用volatile,CAS对于单个变量的控制还是很有效的
3.怎么回收已经下发,但未执行的任务?这就牵扯到另一件事了,需要获取线程的状态(也可以是任务的执行状态,因为这里目标是回收未执行的任务),这就不得不引入了更多的volatile变量,然后这个状态变量,还必须在每次执行任务的时候进行修改,就变成了一个很大的消耗了(这是不能容忍的)
注:这里不能用Thread.getState()来获得线程的状态,该状态是用以系统监控的,并不是用来控制逻辑的(也就是说它不准确)
4.空闲线程队列如果打算重新实现,这又不可避免地需要加入更多的volatile和与程序逻辑相关的happen-before关系了,这会让本来的程序逻辑变得模糊,而且出现问题的几率变得非常大
5.因为要对每个线程进行interrupt,所以又不得不再维护一个线程队列,所以又不得不
……

发现实现当初的目标越来越麻烦了

带着这些问题,最终还是去看了ThreadPoolExecutor的实现,看看大师是怎么做的

发现ThreadPoolExecutor的性能优化只做在了减少锁的范围上,使用的是ReentrantLock,而大部分的参数都使用了volatile,目的只是为了保证可见性,几乎没有出现利用happen-before规则的代码,也就是说,在ThreadPoolExecutor的实现上,优化程度只是在锁的级别上,并没有考虑进一步的优化。而查看类的作者,发现是Doug Lea

回头想想,的确,线程池是不同于AQS的框架的,因为该线程池的实现更注重的是稳定,可复用性,灵活性,安全性。一般说来,线程池调用时间与真正任务的执行时间不是一个数量级的,所以又何必去太计较那点性能的消耗呢?
因此之前所说的“运行速度是ThreadPoolExecutor的1.5倍”的原因也就出来了:任务太简单了,所以大大提高了任务调用时间所占的比例,在现实生活中应该是很少出现这样的情况的。而又说明了另一点,每样功能的实现都是要消耗性能的(特指在并发中),所以在写要求高性能的相关的实现时,应该尽量将需求给落实了,减少功能的需求,才能得到更好的性能

这里再写一下看ThreadPoolExecutor的收获
1.它可以让你注册一个RejectedExecutionHandler,来控制你的下发被拒绝的任务
2.它允许你注册一个threadFactory,来创建线程(这样可以自由地订制自己所需要的线程)
3.里面有一个方法很值得学习的:
我们在正常逻辑中,会有一些常见场景和例外场景(像可能常见场景不需要加锁,但例外场景需要),例外场景应该用尽量少的变量来判断是否进入,而常见场景应该与例外场景完全分离(即使有方法可以重用)
举个例子
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
            if (runState == RUNNING && workQueue.offer(command)) {//常见场景
                if (runState != RUNNING || poolSize == 0)//判断是否进入例外场景
                    ensureQueuedTaskHandled(command);//例外场景,有加锁动作
            }
            else if (!addIfUnderMaximumPoolSize(command))
                reject(command); // is shutdown or saturated
        }
    }

4.类中定义的变量,都用注释说明了被哪一个锁守卫的(也就是说使用上,必须在在这个锁lock状态下才允许修改),这让我想起了《java concurrency in practice》中对使用并发相关annotation的建议,之后再去找找网上有没有提供这个库,如果在编译期就能根据annotation检查错误那就更好了
5.使用了BlockingQueue接口的原有定义来实现:为了减少线程的切换消耗,每个线程在任务队列空了之后,再进行一定时间的等待。(虽然没有知道明确地定义,但根据之前阅读的一些文章,大概推断jvm的实现上,如果时间很短,是不会将线程切换出去的,而是用一个循环机制来实现的)
6.线程的等待与唤醒也是通过BlockingQueue来实现的,ThreadPoolExecutor只维护池的状态,并不会维护线程的任何状态信息,线程的基本动作是由BlockingQueue控制的,这其实在一定的程度上,将并发的复杂度降低了,让ThreadPoolExecutor的功能变得更简单,更容易理解,更容易扩展了
7.优化关键步骤:像线程池,最关键的步骤(也就是被调用次数最多,整个实现中消耗最大的部分)就是getTask,execute,和runTask方法了,在其他方法中,可以没必要去追求什么效率,因为调用次数太少了,像shutdown(就一次),或者addThread,都是很少的,所以为了避免错误,我们可以考虑加锁。但对于关键步骤,我们应该尽可能地优化,少加锁或者不加锁,像3所说的小技巧
8.ThreadPoolExecutor也并没有回收已下发到线程中的任务,而是将它运行完(也就是在getTask动作跟runTask动作之间,并没有什么机制来保证能及时发现关闭事件)

总的来说,之前所制定的计划——实现一个高效而功能强大的线程池——还是有点问题。
就算是JDK的ThreadPoolExecutor,也做不到这一点。每个功能的实现是应该有具体需求的,是优先考虑性能还是其他,都是要有依据的,优先一方面就得牺牲另一方面。像JDK的ThreadPoolExecutor也是为了实现可复用,稳定,全功能,牺牲了一定程度的性能。
0
0
分享到:
评论
1 楼 zq3062211015 2014-03-27  
楼主是大神,膜拜啊,不过有个地方说的不太对啊,

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {  !!!其实这里就加锁了
            if (runState == RUNNING && workQueue.offer(command)) {//常见场景 
                if (runState != RUNNING || poolSize == 0)//判断是否进入例外场景 
                    ensureQueuedTaskHandled(command);//例外场景,有加锁动作 
            } 
            else if (!addIfUnderMaximumPoolSize(command)) 
                reject(command); // is shutdown or saturated 
        } 

相关推荐

    PPT模板 -龙湖新员工转正答辩模板.pptx

    PPT模板 -龙湖新员工转正答辩模板.pptx

    PPT模板 -生产计划管理.pptx

    PPT模板 -生产计划管理.pptx

    生产单元数字化改造23年国赛

    生产单元数字化改造23年国赛

    ECharts柱状图-极坐标系下的堆叠柱状图2.rar

    图表效果及代码实现讲解链接:https://blog.csdn.net/zhangjiujiu/article/details/143997013

    机器人算法的 Python 示例代码 .zip

    Pythonbot高斯网格图射线投射网格图激光雷达至网格地图k-均值对象聚类矩形接头大满贯迭代最近点 (ICP) 匹配FastSLAM 1.0路径规划动态窗口方法基于网格的搜索Dijkstra 算法A* 算法D*算法D* Lite 算法位场算法基于网格的覆盖路径规划国家网格规划偏极采样车道采样概率路线图(PRM)规划快速探索随机树(RRT)回程时间*RRT* 和 reeds-shepp 路径LQR-RRT*五次多项式规划Reeds Shepp 规划基于LQR的路径规划Frenet 框架中的最佳轨迹路径追踪移动到姿势控制斯坦利控制后轮反馈控制线性二次调节器 (LQR) 速度和转向控制模型预测速度和转向控制采用 C-GMRES 的非线性模型预测控制手臂导航N关节臂对点控制带避障功能的手臂导航航空导航无人机三维轨迹跟踪火箭动力着陆双足动物倒立摆双

    sql综合学习基础知识及练习题考试题实测题.zip

    SQL,全称为结构化查询语言(Structured Query Language),是用于管理和操作关系型数据库的标准化语言。它广泛应用于数据插入、查询、更新和删除等操作,并且拥有超过40年的历史,证明了其在数据处理领域的核心地位。以下是对SQL综合学习基础知识及练习题考试题实测题的介绍

    java面向对象 - 类与对象.doc

    java面向对象 - 类与对象 在Java编程语言中,面向对象编程(OOP)是一个核心概念。它强调以对象作为程序的基本单位,并将相关的数据和功能封装在对象中。类和对象是Java OOP的两个关键组成部分。 ### 类(Class) 类是一个模板或蓝图,它定义了对象的属性和行为。我们可以将类视为对象的类型或种类。通过类,我们可以创建(实例化)具有特定属性和行为的对象。 类的组成部分通常包括: 1. **成员变量**(属性):用于存储对象的状态或数据。 2. **方法**(行为):定义了对象可以执行的操作或功能。 3. **构造方法**:一种特殊类型的方法,用于在创建对象时初始化其状态。 4. **块**(如静态块、实例初始化块):用于执行类级别的初始化代码。 5. **嵌套类**:一个类可以包含其他类,这被称为嵌套或内部类。 ### 对象(Object) 对象是类的实例。它是根据类模板创建的具体实体,具有自己的状态和行为。每个对象都是其类的一个唯一实例,可以访问其类中定义的属性和方法。 创建对象的过程通常涉及以下几个步骤: 1. **声明**:指定对象的类型(即其所属的类

    原生JS实现鼠标感应图片左右滚动代码.zip

    原生JS实现鼠标感应图片左右滚动代码.zip

    随机密码生成器,支持字符、数字、字母大小写组合

    随机密码生成器,支持字符、数字、字母大小写组合

    自动化部署管道创建的代码库(含 Concourse 和 Jenkins 相关).zip

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。

    高等工程数学试题详解:矩阵分析与最优化方法

    内容概要:本文档为一份高级数学复习试题,内容涵盖线性代数、数值分析及最优化理论等领域,主要包括矩阵范数的计算、遗传算法中的变异操作、内点法解非线性优化问题、证明矩阵有互异特征值、求解矩阵的标准形以及应用单纯形法和FR共轭梯度法解决具体的数学问题等方面。 适合人群:正在备考研究生入学考试或者准备参加各类数学竞赛的学生、对高等数学感兴趣的学习者及从事相关领域科研工作的专业人士。 使用场景及目标:用于巩固和检验个人关于矩阵论、优化方法及概率统计的知识掌握情况,帮助应试者系统地复习相关考点,提高解题技巧。 阅读建议:建议结合具体题目深入理解每一个概念及其应用方式,遇到复杂的计算或证明步骤不妨动手尝试推导一次,这样有助于加深记忆并培养灵活运用知识的能力。同时,在理解算法原理的基础上,还可以参考一些实际案例来进行练习。

    使用了脉冲码调制(PCM).计算了所需的比特率和信号量化误差Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    Google 表格 Python API.zip

    Google Spreadsheet Python API v4Google Sheets 配合使用的简单界面。特征通过标题、关键字或URL打开电子表格。读取、写入和格式化单元格区域。共享和访问控制。批量更新。安装pip install gspread要求Python 3.8+。基本用法在 Google API 控制台中创建凭据开始使用 gspreadimport gspreadgc = gspread.service_account()# Open a sheet from a spreadsheet in one gowks = gc.open("Where is the money Lebowski?").sheet1# Update a range of cells using the top left corner addresswks.update([[1, 2], [3, 4]], "A1")# Or update a single cellwks.update_acell("B42", "it's

    AICon 2024全球人工智能开发与应用大会(脱敏)PPT合集(30份).zip

    AICon 2024全球人工智能开发与应用大会(脱敏)PPT合集,共30份。 AI辅助编程测评与企业实践 SmartEV和AI 蔚来的思考与实践 下一代 RAG 引擎的技术挑战与实现 书生万象大模型的技术演进与应用探索 人工智能行业数据集构建及模型训练方法实践周华 全方位评测神经网络模型的基础能力 千亿参数 LLM 的训练效率优化 向量化与文档解析技术加速大模型RAG应用落地 基于大模型的缺陷静态检查 多环境下的 LLM Agent 应用与增强 大模型在华为推荐场景中的探索和应用 大模型在推荐系统中的落地实践 大模型的异构计算和加速 大模型辅助需求代码开发 大语言模型在法律领域的应用探索 大语言模型在计算机视觉领域的应用 大语言模型的幻觉检测 小米大模型端侧部署落地探索 快手可图大模型的技术演进与应用探索 提升大模型知识密度,做高效的终端智能 电商大模型及搜索应用实践 百度大模型 原生安全构建之路 硅基流动高性能低成本的大模型推理云实践 语言模型驱动的软件工具思考:可解释与可溯源 长文本大模型推理实践:以 KVCache 为中心的分离式推理架构 阿里云 AI 搜索 RAG 大模型优

    子弹打穿金属后留下弹痕flash动画.zip

    子弹打穿金属后留下弹痕flash动画.zip

    雷达目标一维距离像仿真实验,以及多目标成像 matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    原生js竖直动画手风琴下拉菜单代码.zip

    原生js竖直动画手风琴下拉菜单代码.zip

    受循环荷载作用的土壤或路面层分析Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    Centos6.x通过RPM包升级OpenSSH9.7最新版 升级有风险,前务必做好快照,以免升级后出现异常影响业务

    Centos6.x通过RPM包升级OpenSSH9.7最新版 升级有风险,前务必做好快照,以免升级后出现异常影响业务

    营销策划 -阿道夫洗护品牌新品小红书新品营销方案.pptx

    营销策划 -阿道夫洗护品牌新品小红书新品营销方案.pptx

Global site tag (gtag.js) - Google Analytics