Java并发编程 - 常用容器(ConcurrentHashMap, ConcurrentLinkedQueue)

并发容器的原理,7大并发容器详解、及使用场景 并发容器的原理,7大并发容器详解、及使用场景 http://youzhixueyuan.com/use-of-concurrent-containers.html 并发容器的由来 在Java并发编程中,经常听到Java集合类,同步容器、并发容器,那么他们有哪些具体分类,以及各自之间的区别和优劣呢? 只有把这些梳理清楚了,你才能真正掌握在高并发的环境下,正确使用好并发容器,我们先从Java集... 阅读详情

ConcurrentHashMap的实现原理与使用

传统的HashMap不是线程安全的, 所以多线程进行put()和get()操作的时候可能会引发问题.
还有一个叫做HashTable的数据结构, 它使用的是synchronized来保证线程安全, 但是效率很低, 因为不能并发读.

ConcurrentHashMap采用的是锁分段技术, 将数据分成一段一段存储, 然后给每一段数据配一把锁,
当一个线程对某段数据进行写操作的时候, 其他段的数据也能被其他线程安全访问(读写).

ConcurrentHashMap的结构

  • 一个ConcurrentHashMap包含一个Segment数组;
  • 一个Segment里面包含一个HashEntry链表.

Segment是一种可重入锁, 在ConcurrentHashMap中扮演锁的角色.
HashEntry则是实际存储键值对数据的地方. 当对HashEntry的数据进行修改时, 必须首先获得与它对应的Segment锁.

ConcurrentHashMap

ConcurrentHashMap的初始化

初始化segments数组

if (concurrencyLevel > MAX_SEGMENTS) { // 如果自定义的concurrencyLevel超过允许最大的并发数
    concurrencyLevel = MAX_SEGMENTS; // 设定为允许的最大的并发数
}
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
    ++sshift;
    // 为了能够使用按位与的散列算法来定位segments数组的索引, 必须保证segments数组的长度是2的N次方
    // 例如concurrencyLevel是14, 15或者16, 那么锁的个数也总是16
    ssize <<= 1;
}
segmentShift = 32 -sshift; // 用于定位参与散列运算的位数长度
segmentMask = ssize - 1; // 散列运算的掩码, 各个二进制位都是1
this.segments = Segment.newArray(ssize); // 创建Segment数组

初始化单个segment

if (initialCapacity > MAXIMUM_CAPACITY) { // 如果自定义的Capacity超过了单个segment允许的最大的Capacity
    initialCapacity = MAXIMUM_CAPACITY; // 设定为单个segment允许的最大的Capacity
}
int c = initialCapacity / ssize; // 计算单个segment下的HashEntry数组的长度基值
if (c * ssize < initialCapacity) { // 确保数组能够容纳所有键值对
    ++c;
}
int cap = 1;
while (cap < c) {
    cap <<= 1; // 将HashEntry的长度向2的N次方对齐
}
for (int i = 0; i < this.segments.length; ++i) {
    // 初始化各个segment元素, 实际上是创建cap长度的HashEntry数组, loadFactor指定了HashEntry数组总体上允许的最大负载百分比
    this.segments[i] = new Segment<K, V>(cap, loadFactor)
}

定位Segment
在插入和获取元素的时候, 必须先通过散列算法定位到Segment,
ConcurrentHashMap首先使用Wang/Jenkins hash的变种算法对元素的hashCode进行一次再散列.

变种算法的代码如下:

// 本段代码无需理解, 只需要知道是一个对要插入ConcurrentHashMap元素的原始hash值的再散列(hash)的过程即可
private static int hash(int h) {
    h += (h << 15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h << 3);
    h ^= (h >>> 6);
    h += (h << 2) + (h << 14);
    return h ^ (h >>> 16);
}
/* 用再散列后的元素的的hash值来定位segment数组索引 */
final Segment<K, V> segmentFor(int hash) { // hash 是再散列后的元素的hash值
    // 使用hash值的高四位进行Segment数组索引的计算
    // 这里假设segmentMask为1111, segmentShift为28, 也就是Segment数组的长度为16
    return segments[(hash >>> segmetnShift) & segmentMask]; 
}

之所以进行再散列, 目的是减少散列冲突, 使元素能够均匀分布在不同的Segment上. 如若负载不均,
那么分段锁就会失去意义.

ConcurrentHashMap的操作

主要介绍三种操作: get, put和size操作

get操作

步骤如下:

  • 对元素的原始hash值先进行再散列;
  • 通过这个散列值定位到segment数组索引;
  • 通过散列算法定位到具体的HashEntry数组中的元素;
public V get(Object key) {
    int hash = hash(key.hashCode()); // 再散列
    return segmentFor(hash).get(key, hash); // 点调用segmentFor返回具体的segment, 然后通过hash值获取元素
}

需要注意的是, 定位Segment参与计算是再散列后的hash值的高位, 而定位HashEntry参与计算的是再散列后的has值的低位, 这是为了避免两次计算使用的hash值相同,
导致元素在Segment中散开了, 但是没有在HashEntry中散开.

(hash >>> segmentShift) & segmentMask; // 定位Segment数组索引使用高位
hash & (tab.length - 1); 定位HashEntry使用低位

get的高效之处在于整个get过程不需要加锁, 因为value和count都是volatile类型的值. 所以可以多线程并发读,
但是写入仍然是独占的(对每个数据段独占).

put操作

put操作是需要加锁的. 需要完成以下操作:

  1. 通过再散列的hash值定位到需要插入的Segment;
  2. 判断是否需要对Segment的HashEntry数组进行扩容(通过判断是否超过 负载因子*总容量 来判断);
  3. 定位添加元素的位置;

在扩容的时候, 首先会在当前需要扩容的Segment中创建一个容量是之前两倍的数组, 然后将原数组里的元素进行再散列后插入到新的数组里.
为了高效, ConcurrentHashMap不会对整个容器进行扩容, 仅会对某个segment进行扩容.

size操作

size操作获取count的累加值的具体步骤是:

  1. 先通过两次不锁住segment数组的来累加count;
  2. 如果统计的过程中,
    容器的count出现了变化, 则再采用加锁的方式统计Segment数组中所有元素的个数.

上述的判断容器的count是否发生变化是通过一个modCount变量来统计的, 任何会造成count变化的操作, 比如put, remove,
clean方法操作元素都会将modCount加1, 所以在进行size操作前后比较modCount元素是否发生变化,
就可以得知容器的大小是否发生变化.

ConcurrentLinkedQueue

是一个线程安全的基于链接节点的非阻塞FIFO无界队列.

对于阻塞算法, 有两种实现思路:

  1. 使用一个锁, 也就是入队出队用同一个锁
  2. 使用两个锁, 入队出队用两个锁;

对于非阻塞算法, 可以借助循环CAS的方式来实现(注: 实际上是通过加轻量级锁的方式, 循环CAS会消耗较多的时间片, 只有确认同步块运行速度较快的情况才能使用轻量级锁).

以下是ConcurrentLinkedQueue的结构

ConcurrentLinkedQueue

阻塞队列

阻塞队列是支持阻塞的插入和阻塞的移除的队列.

阻塞的插入 意思是当队列满时, 队列会阻塞插入元素的线程, 直到队列不满;
阻塞的移除 意思是在队列为空时, 获取元素的线程会等待队列变为非空;

常见的应用场景是生产者和消费者的场景, 作为二者获取元素的容器.

Java中的阻塞队列

  • ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
  • LinkedBlockingQueue 一个由链表结构组成的有界(初始化和最大值均为Integer.MAX_VALUE大小, 可以看做无界队列)阻塞队列
  • PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
  • DelayQueue 一个使用优先级队列实现的无界阻塞队列, 在创建元素的时候可以指定多久才能从队列中获取当前元素,
    只有在延迟期满才能从队列中提取元素.
  • SynchronousQueue 一个不存储元素的阻塞队列, 也就是容量是0, 直接将生产者线程的数据传递给消费者.
    每一个put操作必须等待一个take操作, 否则会被阻塞
  • LinkedTransferQueue 一个由链表结构组成的无界阻塞队列, 如果有消费者在等待数据, 就直接将生产者的数据传递给消费者,
    如果没有消费者在等待获取数据, 就将数据放置在队列尾部.
  • LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列. 由于两端都可以入队和出队, 比前述队列减少了一半的数据竞争,
    可以用在"工作窃取"模式中

未完待续

GEOSHIELD:分布式CPS的拜占庭容错与安全防护技术 分布式系统在关键基础设施(如轨道交通、智能电网)中面临网络延迟和恶意节点的双重挑战。拜占庭容错(BFT)技术通过算法保证系统在存在故障节点时仍能达成一致,是保障系统安全的核心机制。GEOSHIELD创新性地结合概率化网络延迟测量和TGS动态信任评分,实现了不依赖可信硬件的拜占庭 resilience。该技术通过建立节点行为的信用档案,动态识别恶意行为,在保证安全性的同时最大化系统可用性。实验证明,即使在自适应攻击策略下,GEOSHIELD仍能保持系统95.6%的正常运行时间,为CPS系统提供了可靠的安全防护 阅读详情

相关推荐

Abel逆变换及其求解方法

Abel逆变换常应用在轴对称的、光学薄的火焰、羽流或等离子体重建。

weixin_49783507的博客 8371

[Map系列]HashMap、ConCurrentHashMap、LinkedHashMap深入理解

自己思考+看源码+其他博客中整理,面试的时候答这一套还是很容易过关,但是TreeMap和weakHashMap还没整理,后面整理好了再发。 HashMap、ConcurrentHashMap、LinkedHashMap理解 一、HashMap 1.底层是数组和链表 2.参数 容量:默认大小为16 负载因子 :0.75,即当 HashMap 的 size &gt; 16*0.75 时就会发生扩容(...

qq_35643562的博客 689

Nest的限流方案

多维度限流(如 IP、用户 ID、接口级别、请求类型)可配置化(支持不同环境和接口动态配置)可观测性(接入日志、监控系统)高性能和分布式支持(Redis 限流器)与鉴权鉴权链路集成(结合 JWT、API Token)以下是一个“标准// 优先按用户ID限流,否则按IP在main.ts维度建议限流存储推荐 Redis 支持多实例限流 Key 设计支持 IP + 用户ID + 路由等多种维度限流策略精细化配置,接口粒度调整异常处理自定义响应体、日志告警观测性。

Bruce__taotao的博客 1196

ConcurrentHashMap使用场景、总结、对比Hashtable、ConcurrentSkipListMap、几种队列;J.U.C线程池、七个参数、四种线程池

ConcurrentHashMap使用场景 ConcurrentHashMap通常只被看做并发效率更高的Map ,用来替换其他线程安全的Map容器,比如Hashtable和Collections.synchronizedMap.线程安全的容器.特别是Map ,很多情况下一个业务中涉及容器的操作有多个,即复合操作,而在并发执行时,线程安全的容器只能保证自身的数据不被破坏,和数据在多个线程间是可见的,但无法保证业务的行为是否正确。 ConcurrentHashMap总结: ●HashMap是线程不安全的.

qq_42722241的博客 3156

Ubuntu22.04下ROS2与PX4仿真环境搭建全流程解析

本文详细解析了在Ubuntu22.04系统下搭建ROS2与PX4仿真环境的全流程,包括系统准备、ROS2 Humble安装、PX4飞控环境部署、ROS2与PX4通信桥梁搭建等关键步骤。针对常见问题提供了解决方案,并分享了性能优化和开发调试技巧,帮助开发者快速构建无人机仿真开发环境。

weixin_29248653的博客 607

Android GWP-Asan机制详解

基本用途:GWP-ASan是一种原生内存分配器功能,在内存分配时额外记录一些信息,用于检测DOUBLE_FREE、USE_AFTER_FREE、INVALID_FREE、BUFFER_OVERFLOW、BUFFER_UNDERFLOW五种内存错误问题。

专注于系统快稳省领域 4145

Java~并发容器ConcurrentHashMap、ConcurrentLinkedQueue、阻塞队列BlockingQueue的实现原理与使用

无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。一个人可以走的很快,但一群人才能走的更远。

ccc6553584的博客 940

阿里奇门接口联调全流程详解:从沙箱自测到正式上线的保姆级攻略

本文详细解析阿里奇门接口联调的全流程,从沙箱自测到正式上线的关键步骤与避坑指南。涵盖环境配置、业务场景测试、跨团队协作、灰度发布及生产运维监控,帮助开发者高效完成API对接,确保供应链协同系统的稳定运行。特别适合首次接触奇门对接的技术负责人参考。

weixin_42535461的博客 416

Java并发编程中的ConcurrentHashMap与ConcurrentLinkedQueue

1.背景介绍 1. 背景介绍 在Java并发编程中,我们经常需要使用并发数据结构来实现线程安全的并发操作。这篇文章将深入探讨Java并发编程中的两个重要并发数据结构:ConcurrentHashMap和ConcurrentLinkedQueue。我们将讨论它们的核心概念、算法原理、最佳实践以及实际应用场景。 2. 核心概念与联系 2.1 ConcurrentHashMap Concurr...

AI天才研究院 1165

【深度学习|学习笔记】广义线性模型Generalized linear model(GLM)模型详解,附代码。

【深度学习|学习笔记】广义线性模型Generalized linear model(GLM)模型详解,附代码。

2401_89898861的博客 1876

Vibe Coding 实战指南:用 3 类社区资源实现每周高效追新

介绍一种分层技术雷达校准方法,通过领域过滤器(L1技术栈锚定、L2能力映射、L3场景收敛)精准筛选AI编程相关更新,结合可验证的Shell命令与CI任务卡,将信息过载压缩为可执行、可审计的工程实践。

weixin_27251469的博客 330

java面试整理(四)—— HashMap、LinkedHashMap、TreeMap、Hashtable、HashSet和ConcurrentHashMap区别

HashMap HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键(HashMap最多只允许一条记录的键为null,允许多条记录的值为null。)。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap中不允许出现重复的键(Key) Hashmap是非线程安全的,如果多个线程同时访问一个HashMap,可能会导致数...

落尘 9783

仿真学习系列(三十八):Sigrity仿真之单端S参数提取

S 参数(Scattering Parameters)是高速信号分析中最基础也最常用的指标,能够全面表征走线、过孔、连接器等互连结构的高频行为。在 Sigrity PowerSI 中,Model Extraction 是最常用的 S 参数提取方式,适用于高速差分线、单端线、过孔、参考平面等各种拓扑结构。 本节将通过示例讲解如何在 PowerSI 中完成一次完整的 S 参数提取流程,包括:对象选择、端口设置、频率范围配置以及结果查看。掌握此方法后,即可对任意高速网络执行阻抗、插损、回损、串扰分析,为进一步的

案例文章,高速信号 838

Java并发编程12--并发容器

并发容器详解

2301_81453175的博客 1048

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

ConcurrentHashMap和CopyOnWriteArrayList

一、并发容器简介 并发容器 对应的普通容器 描述 ConcurrentHashMap HashMap Java 1.8 之前采用分段锁机制细化锁粒度,降低阻塞,从而提高并发性;Java 1.8 之后基于 CAS 实现。 ConcurrentSkipListMap SortedMap 基于跳表实现的 CopyOnWriteArrayList ArrayList CopyOnWriteArraySet Set 基于 CopyOnWriteArrayList实现。 Concurr

学而不思则忘 986

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【Java并发】并发集合

  ConcurrentHashMap   ConcurrentLinkedQueue    

保持学习、思考和实践 280

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

JAVA并发容器与并发工具类

JAVA并发容器与工具笔记1、JAVA并发容器2、JAVA并发容器2.1、CountDownLatch2.2、CyclicBarrier2.3、CyclicBarrier和CountDownLatch的区别2.4、Semaphore2.5、Exchanger 1、JAVA并发容器 ConcurrentHashMap :线程安全的Map。 ConcurrentLinkedQueue:线程安全非阻塞队列。采用先进先出的规 则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部;当我们获取一个元素时,

追梦的疯子 285

Java并发学习(七)并发容器

Java平台类库包含了大量的并发基础构建模块,包括线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类(Synchronizer),学习这些基础模块不仅能提高并发编程的能力,更可以从中看到Doug Lea等大师的设计思想。 同步容器类 同步容器类包括Vector、Hashtable以及它们的子类,还有通过Collections.synchronizedXxx的一些工...

cai13070139328的博客 583

java并发编程之四

java并发框架之容器 ConcurrentHashMap ConcurrentHashMap比HashTable高效。HashTable的get方法和put使用同一把锁,使用的是synchronized同 步方法。这样导致在别的线程访问put方法时,访问get方法的线程也会阻塞。而ConcurrentHashMap的get方 法是不用加锁的。get是没有put那么大风险的,get只需保证

淡淡的宁静的博客 320

【Java并发编程二】Java并发包

【Java并发编程二】Java并发包 1.Java容器 1.1.同步容器 Vector ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除...

来杯特仑苏的博客 129

Java-常见并发容器-总结

在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。这和我们之前提到过的读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK 中提供了类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。

Java程序员的知识博客 1174

并发-ConcurrentHashMap容器、ConcurrentSkipListMap 和 ConcurrentSkipListSet并发容器

目录 ConcurrentHashMap. 2 预备知识... 2 Hash. 2 位运算... 2 1.7中原理和实现... 3 ConcurrentHashMap中的数据结构... 3 初始化做了什么事?... 4 在get和put操作中,是如何快速定位元素放在哪个位置的?... 5 get()方法... 6 put()方法... 6 扩容操作... 7 size方法....

小明的Java问道之路 5992

并发的容器

JDK 提供的这些容器大部分在 java.util.concurrent 包中。 ConcurrentHashMap: 线程安全的 HashMap CopyOnWriteArrayList: 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector. ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。 BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个

li237的博客 117

Java学习笔记-并发编程-1.1并发容器类

1.1并发容器类 同步类容器 同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作。 复合类操作如:迭代(反复访问元素,遍历完容器中所有的元素)、跳转(根据指定的顺序找到当前元素的下一个元素)、以及条件运算。 这些复合操作在多线程并发地修改容器时,可能会表现出意外的行为,最经典的便是ConcurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早期迭代器设计的时候并没有考虑并发修改的问题。 使用同步类容器的时候,如果使用增强for循环或

CarSkyDragn的博客 407

java并发编程的艺术

文章目录 1. JAVA并发机制的底层实现原理 1. 锁的状态 2. 原子操作的实现原理 2. java内存模型 1. 基础 2. 重排序 4. JAVA并发编程基础 1. Synchronized底层指令 2. Thread.join 5. JAVA中的锁 1. AbstractQueuedSynchronized(AQS, 同步器) 2. LockSupport 3. Condition 6. JAVA并发容器和框架 1. ConcurrentHashMap 2. ConcurrentLinkedQue

黑马程序员广州中心的专栏 130

java 并发编程艺术摘抄

读完后对其中部分内容表示理解并认同,于是便抄下来以便后来参考。

有趣 1423
上一篇: Java解惑 - sleep和wait/notify机制, 线程阻塞和等待的区别
下一篇: Java并发编程 - 锁 - 原理详解
congyh
博客等级 码龄15年 43粉丝 38原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值