多线程并发的一些常见的使用规范

news2024/12/21 22:09:47

目录

1. 多线程并发使用规范

1.1 指定线程名称

2. 尽量使用线程池

3.不允许使用Executors

4. 正确停止线程

5. 编写可停止的Runnable

6 . Runnable中必须捕获一切异常

 7. 可考虑使用ThreadLocal

8. 缩短锁 

9. 选择分离锁,分散锁甚至无锁的数据结构

10. 推荐】基于ThreadLocal来避免锁

11. 规避死锁风险

12. volatile修饰符,AtomicXX系列的正确使用

13. 延时初始化的正确写法


1. 多线程并发使用规范

1.1 指定线程名称

【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

  1. 创建单条线程时直接指定线程名称

      Thread thread = new Thread();
      thread.setName("a");

     2. 线程池则使用guava或自行封装的ThreadFactory,指定命名规则。

2. 尽量使用线程池

【推荐】尽量使用线程池来创建线程

    除特殊情况,尽量不要自行创建线程,更好的保护线程资源。 

    同理,定时器也不要使用Timer,而应该使用ScheduledExecutorService。因为Timer只有单线程,不能并发的执行多个在其中定义的任务,而且如果其中一个任务抛出异常,整个Timer也会挂掉,而ScheduledExecutorService只有那个没捕获到异常的任务不再定时执行,其他任务不受影响。 

3.不允许使用Executors

【强制】线程池不允许使用Executors去创建,避资源耗尽风险

Executors返回的线程池对象的弊端:

  1. FixedThreadPool和SingleThreadPool:

允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

  1. CachedThreadPool和ScheduledThreadPool:

允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

应通过newThreadPoolExecutor(xxx,xxx,xxx,xxx)这样的方式,更加明确线程池的运行规则,合理设置Queue及线程池的coresize和maxsize,建议使用vjkit封装的ThreadPoolBuilder。

4. 正确停止线程

【强制】正确停止线程

Thread.stop()不推荐使用,强行的退出太不安全,会导致逻辑不完整,操作不原子,已被定义成Deprecate方法。

停止单条线程,执行Thread.interrupt()。

停止线程池:

ExecutorService.shutdown():不允许提交新任务,等待当前任务及队列中的任务全部执行完毕后退出;

ExecutorService.shutdownNow():通过Thread.interrupt()试图停止所有正在执行的线程,并不再处理还在队列中等待的任务。

最优雅的退出方式是先执行shutdown(),再执行shutdownNow(),vjkit的ThreadPoolUtil进行了封装。

注意,Thread.interrupt()并不保证能中断正在运行的线程,需编写可中断退出的Runnable,见规则5。

5. 编写可停止的Runnable

【强制】编写可停止的Runnable

执行Thread.interrupt()时,如果线程处于sleep(),wait(),join(),lock.lockInterruptibly()等blocking状态,会抛出InterruptedException,如果线程未处于上述状态,则将线程状态设为interrupted。

因此,如下的代码无法中断线程:


     public void run(){
         while (true){
            sleep();
         }
      }
      public void sleep(){
         try {
            Thread.sleep(1000);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
      }
  1. 正确处理InterruptException

        因为InterruptException异常是个必须处理的CheckedException,所以run()所调用的子函数很容易吃掉异常并简单的处理成打印日志,但这等于停止了中断的传递,外层函数将收不到中断请求,继续原有循环或进入下一个堵塞。

        正确处理是调用Thread.currentThread().interrupt();将中断往外传递

  public void sleep(){
         try {
            ....
         } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
         }
      }
  1. 主循环及进入阻塞状态前要判断线程状态

6 . Runnable中必须捕获一切异常

【强制】Runnable中必须捕获一切异常

如果Runnable中没有捕获RuntimeException而向外抛出,会发生下列情况:

  1. ScheduledExecutorService执行定时任务,任务会被中断,该任务将不再定时调度,但线程池里的线程还能用于其他任务。

  2. ExecutorService执行任务,当前线程会中断,线程池需要创建新的线程来响应后续任务。

  3. 如果没有在ThreadFactory设置自定义的UncaughtExceptionHanlder,则异常最终只打印在System.err,而不会打印在项目的日志中。

因此建议自写的Runnable都要保证捕获异常;如果是第三方的Runnable,可以将其再包裹一层vjkit中的SafeRunnable。

 7. 可考虑使用ThreadLocal

【强制】全局的非线程安全的对象可考虑使用ThreadLocal存放

全局变量包括单例对象,static成员变量。

著名的非线程安全类包括SimpleDateFormat,MD5/SHA1的Digest。

对这些类,需要每次使用时创建。

但如果创建有一定成本,可以使用ThreadLocal存放并重用。

ThreadLocal变量需要定义成static,并在每次使用前重置。

8. 缩短锁 

【推荐】缩短锁

  1. 能锁区块,就不要锁整个方法体;

  1. 能用对象锁,就不要用类锁。

9. 选择分离锁,分散锁甚至无锁的数据结构

推荐】选择分离锁,分散锁甚至无锁的数据结构

  1. 分离锁:

1)读写分离锁ReentrantReadWriteLock,读读之间不加锁,仅在写读和写写之间加锁;

2)ArrayBase的queue一般是全局一把锁,而LinkedBase的queue一般是队头队尾两把锁。

  1. 分散锁(又称分段锁):

1)如JDK7的ConcurrentHashMap,分散成16把锁;

2)对于经常写,少量读的计数器,推荐使用JDK8或vjkit封装的LongAdder对象性能更好(内部分散成多个counter,减少乐观锁的使用,取值时再相加所有counter)

  1. 无锁的数据结构:

1)完全无锁无等待的结构,如JDK8的ConcurrentHashMap;

2)基于CAS的无锁有等待的数据结构,如AtomicXXX系列。

基于ThreadLocal来避免锁

10. 推荐】基于ThreadLocal来避免锁

比如Random实例虽然是线程安全的,但其实它的seed的访问是有锁保护的。因此建议使用JDK7的ThreadLocalRandom,通过在每个线程里放一个seed来避免了加锁。

11. 规避死锁风险

【推荐】规避死锁风险

对多个资源多个对象的加锁顺序要一致。

如果无法确定完全避免死锁,可以使用带超时控制的tryLock语句加锁。

12. volatile修饰符,AtomicXX系列的正确使用

【推荐】volatile修饰符,AtomicXX系列的正确使用

多线程共享的对象,在单一线程内的修改并不保证对所有线程可见。使用volatile定义变量可以解决(解决了可见性)。

但是如果多条线程并发进行基于当前值的修改,如并发的counter++,volatile则无能为力(解决不了原子性)。

此时可使用Atomic*系列:

但如果需要原子地同时对多个AtomicXXX的Counter进行操作,则仍然需要使用synchronized将改动代码块加锁。

13. 延时初始化的正确写法

【推荐】延时初始化的正确写法

通过双重检查锁(double-checkedlocking)实现延迟初始化存在隐患,需要将目标属性声明为volatile型,为了更高的性能,还要把volatile属性赋予给临时变量,写法复杂。

所以如果只是想简单的延迟初始化,可用下面的静态类的做法,利用JDK本身的class加载机制保证唯一初始化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/653318.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C/C++】标准库相关题型(一)

文章目录 1. vector底层实现原理1.1 类构成1.2 构造函数1.3 插入元素1.4 删除元素1.5 读取元素1.6 修改元素1.7 释放空间 2. vector内存增长机制2.1 特点2.2 内存增长特性2.3 内存增长过程2.4 内存清理2.5 注意事项 3. vector中reserve和resize的区别3.1 共同点3.2 区别3.3 应用…

在 ZBrush 和 Maya 中创建 Chris Hemsworth 的 3D 肖像

今天瑞云渲染小编给大家带来一篇Marius Prsel分享了 Chris Hemsworth 项目背后的工作过程,详细介绍了角色的头部、面部和头发是如何制作的,并解释了如何在 Arnold 中完成渲染,一起来看看吧! 简介 我的名字是Marius Prsel&#xf…

7个理由:从Java8升级到Java17【翻译】

原文地址: 7 Reasons to Migrate from Java 8 to Java 17 释放吧,Java的全部力量。[手动狗头] 简介 从Java8到Java18,Java已经经历了漫长的发展历程(Java20非长期维护版本)。同时也是从Java 8开始,Java生态系统发生…

【LeetCode热题100】打卡第23天:最小覆盖子集

文章目录 【LeetCode热题100】打卡第23天:最小覆盖&子集⛅前言 最小覆盖🔒题目🔑题解 子集🔒题目🔑题解 【LeetCode热题100】打卡第23天:最小覆盖&子集 ⛅前言 大家好,我是知识汲取者&…

三种方法将Word文档转换为PDF文件格式

如何将Word文档转换为PDF文件格式呢?大家在传输文件时,很多人喜欢使用PDF文件格式,因为它非常稳定,不会出现格式混乱的问题。但有些人可能不知道如何进行转换,今天我将介绍三种转换方法,让我们一起来学习一…

从0开始,精通Go语言Rest微服务架构和开发

说在前面 现在拿到offer超级难,甚至连面试电话,一个都搞不到。 尼恩的技术社区中(50),很多小伙伴凭借 “左手云原生右手大数据”的绝活,拿到了offer,并且是非常优质的offer,据说年…

Pytest教程__Hook钩子函数总结(14)

前言 pytest 的钩子函数有很多,通过钩子函数的学习可以了解到pytest在执行用例的每个阶段做什么事情,也方便后续对pytest二次开发学习。 详细文档可以查看pytest官方文档API Reference — pytest documentation 钩子函数总结 第一部分:set…

5、DuiLib组件结构的初探

文章目录 1、DuiLib组件结构的初探 1、DuiLib组件结构的初探 DuiLib 整体的实现不仅仅有控件,还有窗口消息、XML处理等模块,官方曾经过出的一个结构图如下: 图中还是比较详细的描述了 DuiLib 的整体设计,值得注意的部分是 “窗口…

每日一练 | 华为认证真题练习Day61

1、DHCPv6服务器发送的DHCPv6 ADVERTISE报文目的端口号为? A. 548 B. 547 C. 549 D. 546 2、当DHCPv6客户端收到DHCPv6服务器发送的RA报文中的和O标记位取值为下列哪个数值时,DHCPv6客户端采用DHCPv6有状态自动配置获取IPv6地址和其它配置信息&#…

Android libusb库的使用

Download Data Center Software from Total Phase. 1 Aptiv DABR Aptiv acquired Indian Unwired,Delphi Automotive USB Bridge / Hub,Hub中集成了UDC,upstream UDC连接的主机称为A-Host,downstream UDC被B-Host枚举成Relay devi…

一个床垫的故事

这是学习笔记的第 2460篇文章 这是一个床垫的真实故事,想起来还是蛮感慨的,真是太魔幻了。 起因是我哥搬家,有一个很新的品牌床垫,因为新房子那边买家具已经送了一个床垫了,所以就多出来一个床垫,他打算把…

autogpt的使用,还有出现的问题

AutoGPT简介 AutoGPT是一个实验性开源应用程序,展示了GPT-4语言模型的功能。该程序由GPT-4驱动,将LLM“思想”链接在一起,以自主实现您设定的任何目标。作为GPT-4完全自主运行的首批例子之一,AutoGPT突破了人工智能的极限。 注&a…

Opencv-C++笔记 (6) : opencv-图片和视频操作

文章目录 一、读取函数imread二、图片窗口函数namedWindow三、 图片保存Imwrite和显示函数Imshow四、视频数据的读取五、摄像头直接调用 一、读取函数imread cv::Mat cv::imread(const String & filename,int flagsIMREAD_COLOR)filename:需要读取图像的文件名…

Linux运维监控学习笔记4

Zabbix相关的一些概念: Zabbix用户和用户群组: 用户:Zabbix提供多用户管理,不同的用户可以设置不同的权限,不同的语言和不同的报警方式。 1)创建用户:点击“创建用户”按钮: 2&…

NOTA PEG7 Azide,NOTA-七聚乙二醇叠氮,新型双功能整合剂

NOTA PEG7 Azide中NOTA及其衍生物是新型双功能整合剂之一。NOTA及其衍生物具有良好的配位和鳌合能力,可作为过渡金属离子的配体。叠氮化物基团可以参与铜催化的与炔部分的点击化学反应。 聚乙二醇在科研领域运用广泛,聚乙二醇具有良好的水溶性&#xff0…

【FPGA入门】第一篇、Verilog基本语法常识

目录 第一部分、不同的变量类型 1、wire和reg的区别 2、如何对变量进行赋值呢? 3、什么是阻塞?什么是非阻塞? 第二部分、变量位宽的定义 1、各种系统默认情况 2、变量位宽声明方式 3、表明位宽的情况下,赋值方式 4、两个模…

来自一个敲了5年代码的网络安全工程师的自述(目前薪资30K)

本人是一名敲了5年半代码的网络安全工程师,目前在杭州工作,月薪目前是在30.6K左右,经历过两次跳槽,第一次跳槽拿到了12K的offer,第二次跳槽拿到18K的offer。一直到目前为止的30K左右。 说到这里再给大家提个醒&#x…

SpringBoot整合定时任务技术Quartz

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ RequestMapping注解 🚀Quartz应用场…

好消息!PMP证书还没过期的宝子,可以增持免考CSPM-2证书

2021年10月,中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系,开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会(CAS)组织开展的项…

如何使用hexo next主题,新建一个专栏

文章目录 backgroundIntroHow to do that?🎈 background 今天突发奇想,想要在自己的博客中增加一个新的专栏,记录自己的一些随想。起因是不想让博客成为一个纯粹记录技术成长的网站,(毕竟如果真的有人要看…