ConcurrentHashMap杂谈

article/2025/8/22 7:19:08

为什么使用ConcurrentHashMap

  • 在并发编程中使用HashMap可能导致程序死循环,而使用线程安全的HashTable效率又非常低下

线程不安全的HashMap

  • 在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%

  • 死循环案例:

    final HashMap<String, String> map = new HashMap<String, String>(2);
    Thread t = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {new Thread(new Runnable() {@Overridepublic void run() {map.put(UUID.randomUUID().toString(), "");}}, "ftf" + i).start();}}
    }, "ftf");
    t.start();
    t.join();
    

    原因:多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry

效率低下的HashTable

  • HashTable容器使用synchronized来保证线程安全,但在激烈的情况下HashTable的效率非常低下,因为当一个线程访问HashTable的同步方法时,其他线程会进入阻塞或轮询状态。所以竞争越激烈效率越低

ConcurrentHashMap提升并发访问率的方法

  • 使用的是锁分段技术
  • 访问HashTable的线程都必须竞争同一把锁,加入容器里面有多把锁,每一把锁用于锁容器其中一部分数据,当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,可以有效提高并发访问效率,这就是所分段技术。将数据分成一段一段地存储,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问

ConcurrentHashMap的结构

  • ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁(ReentrantLock);HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment结构是一种数组和链表结构。一个Segment里面包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里面的元素,当对HashEntry数组的数据结构进行修改时,必须首先获得与它对应的Segment锁。如图:

在这里插入图片描述

定位Segment

  • ConcurrentHashMap使用Wang/Jenkins hash的变种算法对元素的hashCode进行一次再散列:

    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);
    }
    
    • 进行再散列的目的是减少散列冲突,是元素能够均匀分布在不同的Segment上,从而提高容器的存取效率

ConcurrentHashMap的操作

  • 三种操作:get操作、put操作和size操作

get操作

  • Segment的get操作实现非常简单高效。先经过一个再散列,然后使用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素

    public V get(Object key) {int hash = hash(key.hashCode());return segmentFor(hash).get(key, hash);
    }
    
  • ConcurrentHashMap的get操作相比于HashTable容器的get方法不需要加锁,除非读到的值是空才会加锁重读。它是如何做到的呢? 原因是它的get方法里将要使用的共享变量都定义成volatile类型,如用于统计当前Segement大小的count字段和用于储存值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,保证多个线程读到的是最新的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖与原值)。根据Java内存模型的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值

    transient volatile int count;
    volatile V value;
    

put操作

  • 由于put方法里需要对共享变量进行写入操作,所以为了线程安全,在操作共享变量时必须加锁。put方法首先定位到Segment,然后在Segment里面进行插入操作:
    • 第一步:判断是否需要对Segment里的HashEntry数组进行扩容
    • 第二步定位添加元素的位置,然后将其放在HashEntry数组里
  • HashEntry数组扩容
    • HashEntry在插入元素前会判断Segment里的HashEntry数组是否超过容量,如果超过容量,再进行扩容。而HashMap是在插入元素之后判断容量是否已经到达阈值,如果到达了阈值就进行扩容,但是很可能扩容之后没有新元素插入,这将会是一次无效的扩容
    • 首先会创建一个容量是原来容量两倍的数组,然后将原数组里的元素进行再散列后插入到新的数组里。为了高效,ConcurrentHashMap不会对整个容器进行扩容,而只是对某个segment进行扩容

seze操作

  • 是不是把所有Segment的count相加就可以得到整个ConcurrentHashMap的大小呢?不是的。理由:虽然在相加时可以获取每个Segment的count的最新值,但是可能累加前使用的count发生了变化,导致统计结果不准确。那么是不是就可以对Segment的put、remove、clean方法加锁就行了呢?其实也不是,这种做法非常低效。于是ConcurrentHashMap采用了下面的做法:ConcurrentHashMap先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小
  • 如何判断在统计的时候容器是否发生了变化:使用modCount变量,在put、remove和clean方法操作元素前将modCount+1,在统计后比较modCount是否发生变化。发生变化,则可以判断容器发生了变化,反之。

参考:《Java并发编程的艺术》仅供个人学习,不可用于商业用途,如有侵权,立删


http://chatgpt.dhexx.cn/article/tIHjrlxa.shtml

相关文章

图解ConcurrentHashMap

曾经研究过jkd1.5新特性&#xff0c;其中ConcurrentHashMap就是其中之一&#xff0c;其特点&#xff1a;效率比Hashtable高&#xff0c;并发性比hashmap好。结合了两者的特点。 集合是编程中最常用的数据结构。而谈到并发&#xff0c;几乎总是离不开集合这类高级数据结构的支…

Java集合:ConcurrentHashMap

本篇内容包括&#xff1a;ConcurrentHashMap 概述、ConcurrentHashMap 底层数据结构、ConcurrentHashMap 的使用以及相关知识点。 一、ConcurrentHashMap 概述 ConcurrentHashMap 是 HashMap 的线程安全版本&#xff0c;其内部和 HashMap 一样&#xff0c;也是采用了数组 链表…

Hashtable与ConcurrentHashMap区别

ConcurrentHashMap融合了hashtable和hashmap二者的优势。 hashtable是做了同步的&#xff0c;hashmap未考虑同步。所以hashmap在单线程情况下效率较高。hashtable在的多线程情况下&#xff0c;同步操作能保证程序执行的正确性。 但是hashtable每次同步执行的时候都要锁住整个结…

ConcurrentHashMap 面试题

作者&#xff1a;程序员库森 链接&#xff1a;https://www.nowcoder.com/discuss/591527?source_idprofile_create_nctrack&channel-1 来源&#xff1a;牛客网 本文汇总了常考的 ConcurrentHashMap 面试题&#xff0c;面试 ConcurrentHashMap&#xff0c;看这一篇就够了…

Hashmap和ConcurrentHashmap的区别

HashTable &#xff08;1&#xff09;底层数组链表实现&#xff0c;无论key还是value都不能为null&#xff0c;线程安全&#xff0c;实现线程安全的方式是在修改数据时锁住整个HashTable&#xff0c;效率低&#xff0c;ConcurrentHashMap做了相关优化 &#xff08;2&#xff0…

ConcurrentHashMap的作用与用法

ConcurrentHashMap的作用与用法 一.ConcurrentHashMap简介 ConcurrentHashMap是属于JUC工具包中的并发容器之一&#xff0c;在多线程开发中很经常会使用到这个类&#xff0c;它与HashMap的区别是HashMap是线程不安全的&#xff0c;在高并发的情况下&#xff0c;使用HashMap进行…

Java并发包concurrent——ConcurrentHashMap

目录 1. ConcurrentHashMap的实现——JDK7版本 1.1 分段锁机制 1.2 ConcurrentHashMap的数据结构 1.3 ConcurrentHashMap的初始化 1.3.1 初始化ConcurrentHashMap 1.3.2 初始化Segment分段 1.4 定位Segment 1.5 ConcurrentHashMap的操作 1.5.1 get 1.5.2 put 1.5.3 …

Java8 ConcurrentHashMap详解

点个赞&#xff0c;看一看&#xff0c;好习惯&#xff01;本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录&#xff0c;这是我花了 3 个月总结的一线大厂 Java 面试总结&#xff0c;本人已拿大厂 offer。 另外&#xff0c;原创文章首发在我的个人博客&#x…

HashMap与ConcurrentHashMap的区别

从JDK1.2起&#xff0c;就有了HashMap&#xff0c;正如前一篇文章所说&#xff0c;HashMap不是线程安全的&#xff0c;因此多线程操作时需要格外小心。 在JDK1.5中&#xff0c;伟大的Doug Lea给我们带来了concurrent包&#xff0c;从此Map也有安全的了。 ConcurrentHashMap具体…

concurrenthashmap实现原理

1.JDK 1.7 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成 Segment 继承自 ReentrantLock&#xff0c;是一种可重入锁&#xff1b;其中&#xff0c;HashEntry 是用于真正存储数据的地方 static final class Segment<K,V> extends ReentrantLock i…

HashMap和ConcurrentHashMap

前言 Map 这样的 Key Value 在软件开发中是非常经典的结构&#xff0c;常用于在内存中存放数据。 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器&#xff0c;在正式开始之前我觉得有必要谈谈 HashMap&#xff0c;没有它就不会有后面的 ConcurrentHashMap。 Hash 表 在…

深入浅出ConcurrentHashMap详解

文章目录 1、前言2、什么是ConcurrentHashMap3、Put 操作4、Get 操作5、高并发线程安全6、JDK8 的改进6.1 结构改变6.2 HashEntry 改为 Node6.3 Put 操作的变化6.4 Get 操作的变化6.5 总结 1、前言 学习本章之前&#xff0c;先学习&#xff1a;深入浅出HashMap详解&#xff08;…

ConcurrentHashMap

ConcurrentHashMap 1.ConcurrentHashMap的出现 我们最常用的集合框架一定包括HashMap&#xff0c;但是都知道它不是线程安全的。在并发插入元素的时候&#xff0c;有可能出现带环链表&#xff0c;让下一次读操作出现死循环。 而想要次避免HashMap的线程安全问题有很多办法&am…

ConcurrentHashMap详解

文章目录 什么是ConcurrentHashMapConcurrentHashMap结构如何高效的执行并发操作如何进行锁的选择Node节点类型与作用扩容的方式 源码分析putVal()方法spread()方法&#xff0c;获取槽位。initTable()方法&#xff0c;初始化容器addCount() &#xff0c;计算成员数量transfer()…

Hudi(四)集成Flink(2)

6、读取方式 6.1、流读&#xff08;Streaming Query&#xff09; 当前表默认是快照读取&#xff0c;即读取最新的全量快照数据并一次性返回。通过参数 read.streaming.enabled 参数开启流读模式&#xff0c;通过 read.start-commit 参数指定起始消费位置&#xff0c;支持指定 …

Spring Boot锦集(三):Spring Boot整合Kafka | Zookeeper/Kafka的安装和配置 | 总结的很详细

前言 在学习本章节前&#xff0c;务必做好以下准备工作&#xff1a; 1、安装并启动了Zookeeper[官网]&#xff0c;如需帮助&#xff0c;点击进入&#xff1b; 2、安装并启动了Kafka[官网]&#xff0c;如需帮助&#xff0c;点击进入。 注&#xff1a;zk和kafka的安装与介绍&…

Flink系列之:Flink CDC深入了解MySQL CDC连接器

Flink系列之&#xff1a;Flink CDC深入了解MySQL CDC连接器 一、增量快照特性1.增量快照读取2.并发读取3.全量阶段支持 checkpoint4.无锁算法5.MySQL高可用性支持 二、增量快照读取的工作原理三、全量阶段分片算法四、Chunk 读取算法五、Exactly-Once 处理六、MySQL心跳事件支持…

大数据面试重点之kafka(三)

Kafka如何保证全局有序&#xff1f; 可回答&#xff1a;1&#xff09;Kafka消费者怎么保证有序性&#xff1f;2&#xff09;Kafka生产者写入数据怎么保证有序&#xff1f;3&#xff09;Kafka可以保证 数据的局部有序&#xff0c;如何保证数据的全局有序&#xff1f;4&#xff0…

Apache Kafka-auto.offset.reset参数(earliest、latest、none)含义说明

文章目录 官方说明参数解读CodePOM依赖配置文件生产者消费者单元测试测试earliestlatest(默认&#xff09;noneexception 源码地址 官方说明 https://kafka.apache.org/documentation/ 选择对应的版本&#xff0c;我这里选的是 2.4.X https://kafka.apache.org/24/documenta…

Kafka之auto.offset.reset值解析

今日在使用kafka时&#xff0c;发现将 auto.offset.reset 设置为earliest、latest、none 都没有达到自己预期的效果。 earliest&#xff1a; 当各分区下有已提交的offset时&#xff0c;从提交的offset开始消费&#xff1b;无提交的offset时&#xff0c;从头开始消费latest&…