并发知识杂谈

news2024/11/24 9:09:16

在JAVA语言层面,怎么保证线程安全?

有序性:使用happens-before原则

可见性:可以使用 volatile 关键字来保证,不仅如此,volatile 还能起到禁止指令重排的作用;另外, synchronized 和 final 这俩关键字也能保证可见性。

原子性:可以使用 和 java.util.concurrent.atomic 包中的原子类来保证。

0、三大关键字:

1、CAS算法:

首先,大家应该已经知道,JMM 中不仅有主内存,每个线程还有各自的本地内存。每个线程会先更新自己的本地内存,然后再同步更新到主内存。

那如果多个线程都想要同步更新到主内存怎么办呢?

CAS 就是用来保证这种情况下的线程安全的。当多个线程尝试使用 CAS 同时更新主内存中的同一个变量时,只有一个线程可以成功更新变量的值,其他的线程都会失败,失败的线程并不会挂起,而是会自旋重试。

1.1 具体来说,CAS(Compare And Set):比较并替换,该算法中有重要的三个操作数:

  • 需要读写的主内存位置(某个变量的值)
  • 该变量原来应该有的值(检查值有没有发生变化)
  • 线程想要把这个变量更新为哪个值

1.2 首先线程先读取需要读写的某个变量的值,然后比较当前该变量的值和该变量原来应该有的值:

  • 如果当前该变量的值与原来应该有的值相匹配,那么这个线程就可以将该变量更新为新值
  • 如果当前该变量的值与原来应该有的值不匹配,那就更新失败,开始自旋重试。

1.3 听起来好像有点拗口,其实总结一下就是三步走:

  • 读取
  • 比较
  • 交换

1.4 CAS 存在的三大问题
看起来 CAS 好像很不错,高效地解决了并发问题,但事实上,CAS 仍然存在三大问题:

  • ABA 问题
  • 循环时间长开销大
  • 只能保证一个共享变量的原子操作

2、AQS(队列同步器):

3、Lock接口:

Java 有两套锁实现,一个就是原生的 synchronized 关键字,另一个就是实现了 Lock 接口的类比如 ReentrantLock。那么既然有了前者,为什么还大费力气整出一套新的实现呢?

对于 synchronized 来说,它把锁的获取和释放操作完全隐藏起来了,进入同步块的时候自动尝试去获取锁,退出同步块时候的自动释放锁,也就是说获取锁操作一定是在释放锁操作之前的。

使用 Lock 那样手动获取和释放锁的方式了。

Lock 接口概览

Lock 其实也没啥神秘的,整个接口就 6 个方法:

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}
  1. lock():尝试获取锁,获取锁成功后返回

  2. lockInterruptibly():可中断的获取锁。所谓可中断的意思就是,在锁的获取过程中可以中断当前线程

  3. tryLock():尝试非阻塞的获取锁。不同于 lock() 方法在锁获取成功后再返回,该方法被调用后就会立即返回。如果最终获取锁成功返回 true,否则返回 false

  4. tryLock(long time, TimeUnit unit):超时的获取锁。如果在指定时间内未获取到锁,则返回 false

  5. unlock():释放锁

  6. newCondition():当前线程只有获得了锁,才能调用 Condition 接口的 await 方法。Condition 接口本文就先不做详细赘述了

4、并发集合:

与现代的数据结构类库的常见情况一样,Java 集合类也将接口与实现分离,这些接口和实现类都位于 java.util 包下。按照其存储结构集合可以分为两大类:

  • 单列集合 Collection
  • 双列集合 Map

Collection 接口

单列集合 java.util.Collection:元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储。

1)List 的特点是元素有序、可重复。注意,这里所谓有序的意思,并不是指集合中按照元素值的大小进行有序排序,而是说,List 会按照元素添加的顺序来进行存储,保证插入的顺序和存储的顺序一致。

List 接口的常用实现类有:

  • ArrayList:底层数据结构是数组,线程不安全
  • LinkedList:底层数据结构是链表,线程不安全

2)Set 接口在方法签名上与 Collection 接口其实是完全一样的,只不过在方法的说明上有更严格的定义,最重要的特点是他拒绝添加重复元素,不能通过整数索引来访问,并且元素无序。同样的,所谓无序也就是指 Set 并不会按照元素添加的顺序来进行存储,插入的顺序和存储的顺序是不一致的。其常用实现类有:

  • HashSet:底层基于 HashMap 实现,采用 HashMap 来保存元素
  • LinkedHashSet:LinkedHashSet 是 HashSet 的子类,并且其底层是通过 LinkedHashMap 来实现的。

3)Queue 这个接口其实用的不多,可以把队列看作一种遵循 FIFO 原则的特殊线性表(事实上,LinkedList 也确实实现了 DeQueue 接口),Queue 接口是单向队列,而 DeQue 接口继承自 Queue,它是双向队列。

Map 接口 

双列集合 java.util.Map:元素是成对存在的。每个元素由键(key)与值(value)两部分组成,通过键可以找对所对应的值。显然这个双列集合解决了数组无法存储映射关系的痛点。另外,需要注意的是,Map 不能包含重复的键,值可以重复;并且每个键只能对应一个值。

来看 Map 接口的继承体系图:

Map 有两个重要的实现类,HashMap 和 LinkedHashMap : 

HashMap:可以说 HashMap 不背到滚瓜烂熟不敢去面试,这里简单说下它的底层结构吧。JDK 1.8 之前 HashMap 底层由数组加链表实现,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法” 解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间(注意:将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)。

LinkedHashMap:HashMap 的子类,可以保证元素的存取顺序一致(存进去时候的顺序是多少,取出来的顺序就是多少,不会因为 key 的大小而改变)。

LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构,即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

线程安全的集合总览

J.U.C 为每一类集合都提供了线程安全的实现,等会往下看各位就会发现很多线程安全的集合都是以 Concurrent 或者 CopyOnWrite 开头的。这里先给大家解释下:

以 Concurrent 开头的集合类通常采用某些比较复杂的算法(以 ConcurrentHashMap 为首,相信各位八股文都已经背恶心了)来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。

而以 CopyOnWrite(COW)开头的集合类采用了 写时复制 的思想来支持并发读。

具体来说,就是我们往一个 COW 集合中添加元素的时候,底层并不是直接向当前的集合中添加,而是先将当前的集合 copy 出一个新的副本,然后在这个副本李添加元素,添加完元素之后,再将原集合的引用指向这个副本。这样做的好处是我们可以对 COW 集合进行并发的读,而不需要执行加锁操作。

所以,CopyOnWrite 集合其实是一种读写分离的集合。

下面我们来捋一下具体都有哪些线程安全的集合:

Collection 接口

先来一张

1)List:

  • Vector(这个没啥好说的,它就是把 ArrayList 中所有的方法统统加上 synchronized )
  • CopyOnWriteArrayList

2)Set:

  • CopyOnWriteArraySet
  • ConcurrentSkipListSet

3)Queue:

  • BlockingQueue 接口

        LinkedBlockingQueue

        DelayQueue

        PriorityBlockingQueue

        ConcurrentLinkedQueue

  • TransferQueue 接口

        LinkedTransferQueue

  • BlockingDeque 接口 

  LinkedBlockingDeque

  ConcurrentLinkedDeque

Map 接口 

  • HashTable(这个有一些细节需要注意,可以看这里 Hashtable 渐渐被人们遗忘了,只有面试官还记得,不过在线程安全方面其实也没啥好说的,就是把 HashMap 中所有的方法统统加上 synchronized )
  • ConcurrentMap 接口

   ConcurrentHashMap
   ConcurrentSkipListMap

ConcurrentHashMap   

JDK1.7

第一句:ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成

第二句:Segment 继承自 ReentrantLock,是一种可重入锁;其中,HashEntry 是用于真正存储数据的地方

第三句:一个 ConcurrentHashMap 包含一个 Segment 数组,一个 Segment 里包含一个 HashEntry 数组,当对某个 HashEntry 数组中的元素进行修改时,必须首先获得该元素所属 HashEntry 数组对应的 Segment 锁

ConcurrentHashMap 采用分段锁(Segment 数组,一个 Segment 就是一个锁)技术,每当一个线程访问 HashEntry 中存储的数据从而占用一个 Segment 锁时,并不会影响到其他的 Segment,也就是说,如果 Segment 数组中有 10 个 元素,那理论上是可以允许 10 个线程同时执行的。

小结
总结下 JDK 1.7 版本下的 ConcurrentHashMap,其实就是数组(Segment 数组) + 链表(每个 HashEntry 是链表结构),存在的问题也很明显,和 HashMap 一样,那就是 get 的时候都需要遍历链表,效率实在太低。

So,JDK 1.8 做了一些结构上的小调整

JDK1.8

不同于 JDK 1.7 版本的 Segment 数组 + HashEntry 链表,JDK 1.8 版本中的 ConcurrentHashMap 直接抛弃了 Segment 锁,一个 ConcurrentHashMap 包含一个 Node 数组(和 HashEntry 实现差不多),每个 Node 是一个链表结构,并且在链表长度大于一定值时会转换为红黑树结构(TreeBin)。

那既然没有使用分段锁,如何保证并发安全性的呢?

synchronized + CAS!

简单来说,Node 数组其实就是一个哈希桶数组,每个 Node 头节点及其所有的 next 节点组成的链表就是一个桶,只要锁住这个桶的头结点,就不会影响其他哈希桶数组元素的读写。桶级别的粒度显然比 1.7 版本的 Segment 段要细。

JDK 1.8 没有使用 ReentrantLock 而是改用 synchronized,足以说明新版 JDK 对 synchronized 的优化确有成效。

5、并发工具类:

6、线程池:

三大方法、7大参数、4种拒绝策略

三大方法:

七大参数 

 

 

三大方法的源码都是用ThreadPoolExcutor来创建的 

所以一般实际运用中药用源码的方式去自定义线程池

 四种拒绝策略

最大线程数如何设置?

分为CPU密集型和IO密集型

CPU密集型:设置CPU核数为最大线程数

IO密集型:程序有15个大型任务,IO特别占用资源,一般设置为大型任务数的二倍

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

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

相关文章

进程和编码

一、python代码的运行方式 1.脚本式 2. 交互式 一般用于代码的测试 二、进制及相互之间的转换 1. 进制 2.进制之间相互转换 在python中,十进制是以整形的形式存在,其他进制是已字符串的形式存在。 二进制/八进制/十六进制都可与十进制相互转换。但…

走向编程大师之路的几个里程碑

走向编程大师之路的几个里程碑 1语言关 2算法关 3系统关 4 编译器关 如下的系统的核心代码都有一万行以上,是规模和复杂度足够 大,可以检验开发者的模块化编程能力,掌控复杂度的能力。 使用什么编程语言本身是不重要的,能够有能…

常用消息中间件简介

一、 分布式系统消息通信技术简介 分布式系统消息通信技术主要包括以下几种: 1. RPC(Remote Procedure Call Protocol). 一般是C/S方式,同步的,跨语言跨平台,面向过程 2. CORBA(Common Object Request Broker Architecture). CO…

一个命令搞定Linux大文件下载

问题 Linux下log日志太大了,下载太慢了,即使下载下来,打开也费劲,咋办?将某文件夹打包成xx.tar.gz包,但依然很大,公司无法下载这么大的压缩包,咋办? split 以上2个问题…

[golang gin框架] 37.ElasticSearch 全文搜索引擎的使用

一.全文搜索引擎 ElasticSearch 的介绍,以 及安装配置前的准备工作 介绍 ElasticSearch 是一个基于 Lucene 的 搜索服务器,它提供了一个 分布式多用户能力的 全文搜索引擎,基于 RESTful web 接口,Elasticsearch 是用 Java 开发的,并作为 Apac…

PIC18F26单片机波特率配置

只需要配置以下三个寄存器: BRGCON1 BRGCON2 BRGCON3 BRGCON10x07; > 0000 0111 BRGCON20x90; > 1001 0000 BRGCON30x42; > 0101 0010 BRGCON1: Sync_Sog (bit7~bit6)1TQ,BRP(bit5~bit0)1 ,则TQ((2*(BRP1))/Fosc16/32M&am…

Mysql存储时间,对应Api及对应的java属性

1.Mysql存储时间的类型 常用的储存时间/日期的类型: DATE:仅用于存储日期值(年、月、日),格式为YYYY-MM-DD。TIME:仅用于存储时间值(小时、分钟、秒),格式为HH:MM:SS。DA…

朴素贝叶斯算法实现英文文本分类

目录 1. 作者介绍2. 朴素贝叶斯算法简介及案例2.1朴素贝叶斯算法简介2.2文本分类器2.3对新闻文本进行文本分类 3. Python 代码实现3.1文本分类器3.2 新闻文本分类 参考(可供参考的链接和引用文献) 1. 作者介绍 梁有成,男,西安工程…

【UE】连续射击Niagara特效

效果 步骤 1. 新建一个粒子系统 选择“来自所选发射器的新系统” 添加“Fountain” 2. 打开这个新建的粒子系统 选中“Initialize Particle”模块,将颜色设置为(100,0,0) 再让生成的粒子大一些 选中“Spawn Rate”模块,将粒子的…

如何编写接口自动化框架系列之unittest测试框架的详解(二)

在编写自动化框架过程中 ,我们首先想到的就是选择一个合适的测试框架 ,目前常用的测试框架有unittest和pytest , unittest比较简单,适合入门着学习 ;而pytest比较强大,适合后期进阶 。本文主要介绍的就是unittest框架 …

pytorch笔记(十)Batch Normalization

环境 python 3.9numpy 1.24.1pytorch 2.0.0+cu117一、Batch Normalize 作用 加快收敛、提升精度:对输入进行归一化,从而使得优化更加容易减少过拟合:可以减少方差的偏移可以使得神经网络使用更高的学习率:BN 使得神经网络更加稳定,从而可以使用更大的学习率,加速训练过程…

Chapter5: SpringBoot与Web开发2

接上一篇 Chapter4: SpringBoot与Web开发1 10. 配置嵌入式Servlet容器 SpringBoot默认采用Tomcat作为嵌入的Servlet容器;查看pom.xml的Diagram依赖图: 那么如何定制和修改Servlet容器的相关配置? 下面给出实操方案。 10.1 application.properties配…

依赖范围和编译classpath、测试classpath、运行classpath的关系

最近学习maven,这里看了下别人解释的区别原文,机翻一下,看的懵懵懂懂的 这其实应该是一个简单的区别,但我一直在Stackoverflow上回答一连串类似的问题,而人们往往会误解这个问题。 那么,什么是classpath&am…

[CF复盘] Codeforces Round 874 (Div. 3) 20230520】

[CF复盘] Codeforces Round 874 (Div. 3 20230520 总结A. Musical Puzzle![在这里插入图片描述](https://img-blog.csdnimg.cn/01ab8d835b4343659e8b80680dd9d639.png)2. 思路分析3. 代码实现 B. Restore the Weather1. 题目描述2. 思路分析3. 代码实现 C. Vlad Building Beaut…

FinClip | 2023 年 4 月产品大事记

我们的使命是使您(业务专家和开发人员)能够通过小程序解决您的关键业务流程挑战。不妨让我们看看在本月的产品与市场发布亮点,看看它们如何帮助您实现目标。 产品方面的相关动向👇👇👇 全新版本的小程序统…

知识图谱实战应用12-食谱领域智能问答系统,实现菜谱问答

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用12-食谱领域智能问答系统,实现菜谱问答,本项目基于py2neo和neo4j图数据库,将知识图谱应用于菜谱领域。通过构建菜谱知识图谱,实现简单的菜谱食材问答系统。用户可以通过问答系统,快速获取简单的菜谱食材信息。 一…

Vivado综合属性系列之十一 GATED_CLOCK

目录 一、前言 二、GATED_CLOCK 2.1 属性说明 2.2 工程代码 2.3 综合结果 一、前言 在工程设计中,时钟信号通常来源于专用的时钟单元,如MMCM和PLL等。但也存在来自逻辑单元的信号作为时钟,这种时钟信号为门控时钟。门控时钟可以降低时…

Linux下V4l2框架编程_USB摄像头数据采集

Linux内核版本:3.5.0 1.1 V4L2简介 v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。 这篇文章介绍V4L2框架读取摄像头数据的流程,介绍ioctl常用的命令参数,以及各种摄像头相关的结构体成员含义,最终完成数据采集。 编程模式如下: V4l2支持多种设备,它可…

项目管理PMP好考吗,没有经验?

现在越来越多的产品经理和开发人员也投入到考PMP的大军中,在真实的项目中也会有很多产品经理兼任项目经理的职责,这点还是比较常见的,如果说产品或者开发人员考了PMP证书,本身也会让你在找工作的大军中更具有优势,俗话…

模电基础学习

模拟电路基础 计算机工作原理 用电去控制电,这是计算机工作的核心原理。 电学基础 软件编程更新迭代特别的快,而硬件的学习可能很多年都没有变化,越老越吃香。电路设计好比老中医,学会一个套路就可以用一辈子,因为电路设计是基于物理学原理一直都没有变化过,现在最常用…