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的初始化

初始化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操作

步骤如下:

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

需要注意的是, 定位Segment参与计算是再散列后的hash值的高位, 而定位HashEntry参与计算的是再散列后的hash值的低位, 这是为了避免两次计算使用的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的结构

这里写图片描述

阻塞队列

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

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

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

Java中的阻塞队列

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

未完待续

一个响应ping包延迟偏大的问题 前段时间客户反馈一个使用pc上的命令行ping无线设备(WiFi4)时,设备平均响应时间(测试时长2小时,1秒Ping一次)相比其它机型偏慢的问题,并上传上具体的时间的Log.从log中看到,大多数包的响应还是比较快的,只是个别响应时间较长,且响应时间长的包时间几乎都是一样的100多ms.如下是部分响应时间截图. 整个2小时的测试时间里,总是时不时的出这么上102,104ms的响应时间,这直接导致了平均响应时间被拉大.于是本地测试,也发现了类似的延迟.本地测试时,现象略有... 阅读详情

相关推荐

AD56XXR系列DAC驱动设计:SPI接口、寄存器配置与嵌入式工程实践

数模转换器(DAC)是嵌入式系统实现高精度模拟控制的核心器件,其性能取决于通信协议可靠性、寄存器配置准确性及硬件协同设计能力。基于SPI总线的电压输出型DAC广泛应用于工业控制、光模块偏置、传感器校准等场景,需兼顾静态精度、动态响应与低功耗特性。AD56XXR系列作为典型集成内部基准与多通道同步更新能力的12/14/16位DAC,支持轨到轨输出与灵活掉电模式,为资源受限MCU提供可移植固件范式。本文围绕SPI时序约束、16位指令字解析、LDAC同步机制及精度补偿策略展开,覆盖从硬件连接、初始化流程到量产级可

weixin_33481022的博客 610

Java并发容器和框架——Java并发编程的艺术读书笔记

这里写目录标题一级目录二级目录三级目录 一级目录 二级目录 三级目录

qq_41523340的博客 301

用化学方法nmp溶剂从佳能cmos传感器上剥离拜耳矩阵和微透镜

将光刻胶暴露于 NMP 溶剂中 10-15 分钟,并进行轻微的机械搅拌,将溶解的光刻胶从基材表面带走,即可去除大部分光刻胶,之后再进行第二次溶剂浴,以洗去微量含有光刻胶的溶剂。我还担心传感器的校准问题,我觉得可以大致解决这个问题,比如在安装面上用刻度尺标记三个螺丝的位置,然后数着拧出螺丝的圈数,直到听到“咔哒”声(螺纹完全拧出)。我还担心传感器的校准问题,我觉得可以大致解决这个问题,比如在安装面上用刻度尺标记三个螺丝的位置,然后数着拧出螺丝的圈数,直到听到“咔哒”声(螺纹完全拧出)。在我看来,这是必读的;

qq_40247880的博客 1473

JUC Queue

ConcurrentLinkedQueue 是线程安全、无界、非阻塞队列,其底层数据结构使用单向链表实现,对于入队和出队操作使用 CAS 来实现线程安全 。 ConcurrentlinkedQueue复习看下表即可 Level. 放入 取出 1 add 入队尾,由于是无界队列,return true remove 队列空?异常:return head 2 offer 同 add poll 队列空?return null:return head 3 ------ ------ pe

javaThinker的专栏 294

MRI图像切片(nii格式转2D)

代码参考:将一个MRI(nii格式)数据进行切片 想要批量抽取MRI数据的几个切片,借鉴了上述代码的Python版本,原始代码抽取了全部切片。本次实验主要是抽取sMRI的横断面(axial)方向的其中30张切片,数量根据自己需求可自行设置。 import scipy, numpy, shutil, os, nibabel import sys, getopt import imageio def niito2D(filepath): inputfiles = os.listdir(filep

chs_502的博客 7548

贝叶斯校准在计算机模型不确定性分析中的实践指南

本文深入探讨了贝叶斯校准在计算机模型不确定性分析中的核心原理与实践方法。通过将模型从“黑箱”转变为“透明箱”,该方法系统量化了参数不确定性、模型结构缺陷及观测误差。文章重点阐释了如何利用高斯过程作为代理模型和模型不足函数,构建完整的贝叶斯校准框架,并提供了从数据准备、模型训练到MCMC推断及不确定性分析的实战指南,最终实现更可靠的预测与决策支持。

weixin_29088655的博客 292

JUC并发容器——ConcurrentLinkedQueue

线程安全的队列 阻塞队列:使用一个锁(进队和出队同一个锁)和两个锁(入队和出队用不同的锁) 非阻塞队列:ConcurrentLinkedQueue ConcurrentLinkedQueue特性: 是一个基于链接节点的无界线程安全队列, 按先进先出的插入顺序进行排序, 它采用“wait-free”算法(CAS算法)实现, 具体实现:添加元素时它会添加到队列尾部;获取元素时,它会返回队列头部的...

cplcdk的博客 169

上位机1000条/秒数据不丢不卡:SQLite持久化队列最优方案实战

在上位机每秒1000条、1MB数据的场景下,SQLite做持久化队列是最优解可靠性拉满,ACID事务+WAL模式保证绝对不丢数据;性能完全够用,优化后每秒几千条没问题;轻量无依赖,只有一个.db文件;C#/.NET原生支持,代码简单易维护。只要按照上面的优化点和代码实现,完全能满足工业现场的需求,不用再担心数据丢失、存储卡采集的问题。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 482

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

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

ccc6553584的博客 940

Open3D 体素下采样【2026最新版】

体素滤波,博客长期更新,本文最近更新时间为:2026年4月11日。

点云侠的博客 37万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Java并发知识点(三)ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList

ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList

HK的博客 647

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

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

并发容器详解

2301_81453175的博客 1048

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

ConcurrentHashMap和CopyOnWriteArrayList

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

学而不思则忘 986

【Java并发】并发集合

  ConcurrentHashMap   ConcurrentLinkedQueue    

保持学习、思考和实践 280

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并发包

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

来杯特仑苏的博客 129

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-常见并发容器-总结

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

Java程序员的知识博客 1171

并发的容器

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

li237的博客 117

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

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

CarSkyDragn的博客 407

并发-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

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
上一篇: Mybatis分页插件pagehelper 5.1.2遇到的问题
下一篇: Java基础之异常(Exception)
章 鱼
博客等级 码龄10年 203粉丝 105原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值