首页 > 其他分享 >ConcurrentHashmap 锁

ConcurrentHashmap 锁

时间:2024-11-24 15:02:05浏览次数:10  
标签:ConcurrentHashmap Java 分段 ConcurrentHashMap 并发 线程 哈希

1. ConcurrentHashMap 的基本原理

1.1 ConcurrentHashMap 的结构

ConcurrentHashMap 是一种线程安全的哈希表。它通过将数据分成多个段(在 Java 8 之前)或桶(Java 8 之后),使得多个线程可以并发地访问不同的部分,从而减少了锁争用,提高了并发性能。

  • Java 7 及之前的实现ConcurrentHashMap 使用了一种称为“分段锁”(Segmented Lock)的技术。它将整个哈希表分成多个段(Segment),每个段其实是一个小的哈希表,并且有自己的锁。这样,多个线程可以同时访问不同的段而不会发生竞争。

  • Java 8 及之后的实现ConcurrentHashMap 移除了分段锁的设计,转而采用一种更精细的锁机制,称为“锁分离”(Lock Striping)。每个桶(bucket)仍然是一个链表或树,但锁的粒度更加细化,锁的争用进一步减少。

1.2 分段锁的工作原理(Java 7)

在 Java 7 及之前的版本中,ConcurrentHashMap 的基本单位是 Segment。每个 Segment 是一个哈希表,它们各自独立,并发访问时通过独立的锁保护:

  • 分段锁定:当线程要访问 ConcurrentHashMap 中的数据时,它会根据哈希值计算出对应的段(Segment),并且仅锁定该段的锁(ReentrantLock)。
  • 分段读取:如果只是读取操作,没有任何锁定操作,因为 ConcurrentHashMap 保证了弱一致性——即读取操作可能会读取到最新写入的值,也可能读取到旧值,但不会导致崩溃。
1.3 锁分离机制(Java 8 及之后)

Java 8 引入了更细粒度的锁管理,使得锁的争用进一步减少,具体方式如下:

  • CAS 操作:使用 Compare-And-Swap(CAS)操作来实现无锁的快速更新操作,如节点插入或更新。CAS 操作能够在多个线程竞争时依然保持高效性。
  • Synchronized + 内置锁:在链表转换为红黑树的过程中,ConcurrentHashMap 通过内置锁来保护单个桶的操作,而不是锁住整个 Segment
  • 减少锁粒度:桶级别的锁使得锁粒度更小,多个线程只要不访问同一个桶,便不会产生锁竞争。

2. 锁的加法与使用

2.1 如何加锁

ConcurrentHashMap 中,锁的加法取决于操作类型:

  • 读取操作:通常不需要加锁,因为读取操作不会影响哈希表的结构,且 ConcurrentHashMap 使用了 volatile 关键字确保变量的可见性。
  • 写入操作:在写入时,ConcurrentHashMap 需要确保哈希表的结构不被破坏,这时会对相关的桶或段加锁。在 Java 8 中,写操作会锁定具体的桶或节点。

示例(Java 8 实现)

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // Calculate the hash value of the key
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // No lock when adding to an empty bucket
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {            // Lock the first node of the bin
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
}

在这个例子中:

  • CAS 操作:用于无锁地尝试更新或插入节点。
  • 同步锁:在需要更新链表或树结构时,加锁保护。
2.2 分段越多越好?

并不是分段(或者说桶)的数量越多越好。分段过多或过少都会影响性能:

  • 分段过少:会导致大量线程争用同一个锁,降低并发性能。
  • 分段过多:每个段或桶的空间开销增大,同时桶的数量与内存占用呈线性关系。此外,哈希冲突的概率增加,导致更多的链表或树操作,反而降低性能。

理想的分段数量取决于系统的实际并发度和可用内存。默认情况下,ConcurrentHashMap 的初始容量和并发级别都设置为合理的默认值,能够满足大部分场景的需求。

3. 适当配置 ConcurrentHashMap

3.1 初始容量和并发级别

ConcurrentHashMap 的初始容量和并发级别应根据应用程序的实际情况来配置:

  • 初始容量:需要根据预期的最大存储元素数量以及装载因子(默认 0.75)来设置,以减少动态扩容的开销。
  • 并发级别(在 Java 7 中适用):设置合理的并发级别,使得多个线程可以同时访问不同的段,提高并发性能。

示例

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(16, 0.75f, 16);

这个示例中:

  • 初始容量为 16
  • 装载因子为 0.75
  • 并发级别为 16
3.2 扩容策略

ConcurrentHashMap 的扩容是渐进式的,不会一次性扩容整个哈希表,而是分成多个步骤,逐步扩容每个桶。这样避免了在高并发场景下扩容造成的性能瓶颈。

4. 总结

ConcurrentHashMap 是一种高效的并发集合,通过分段锁或细粒度锁机制保证了线程安全。它通过合理的锁设计和无锁操作(如 CAS),在高并发环境下提供了优秀的性能。

在选择分段数量时,需要平衡内存开销和并发性能。分段并不是越多越好,合理配置并发级别和初始容量,才能充分发挥 ConcurrentHashMap 的性能优势。Java 8 之后的实现通过进一步细化锁的粒度,使得 ConcurrentHashMap 在处理大量并发读写操作时,性能更加优越,适用于大多数并发场景。

标签:ConcurrentHashmap,Java,分段,ConcurrentHashMap,并发,线程,哈希
From: https://blog.csdn.net/Flying_Fish_roe/article/details/144007694

相关文章

  • ConcurrentHashMap是怎么实现的?
    1.是什么    ConcurrentHashMap 是Java并发包(java.util.concurrent)中的一个线程安全的哈希表实现。与 HashMap 相比,ConcurrentHashMap 在并发环境下具有更高的性能,因为它允许多个线程并发地进行读写操作而不会导致数据不一致。以下是 ConcurrentHashMap 实现的一......
  • 深入理解ConcurrentHashMap
    HashMap为什么线程不安全put的不安全由于多线程对HashMap进行put操作,调用了HashMap的putVal(),具体原因:假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的;当线程A执行完第六行由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正......
  • HashMap线程不安全|Hashtable|ConcurrentHashMap
    文章目录常见集合线程安全性HashMap为什么线程不安全?怎么保证HashMap线程安全HashtableConcurrentHashMap引入细粒度锁代码中分析总结小结常见集合线程安全性ArrayList、LinkedList、TreeSet、HashSet、HashMap、TreeMap等都是线程不安全的。HashTable是线程安......
  • [Java并发]Concurrenthashmap的size()
    1.一致性定义关于一致性的定义,大概如下:一致性(Consistency)是指多副本(Replications)问题中的数据一致性。可以分为强一致性、顺序一致性与弱一致性。1.1强一致性(StrictConsistency)强一致性也被可以被称做:原子一致性(AtomicConsistency)线性一致性(LinearizableConsistency)要......
  • Java中的集合框架深度解析:从ArrayList到ConcurrentHashMap的性能考量
    Java中的集合框架深度解析:从ArrayList到ConcurrentHashMap的性能考量大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!Java的集合框架为开发者提供了多种数据结构,每种数据结构都有其特定的使用场景和性能特征。本文将深度解析Java中的主要集合类,从Array......
  • HashMap和ConcurrentHashMap对比源码分析
    1.1HashMap分析1.1.1JDK7的HashMap HashMap在日常开发中是很常见的,在JDK7中其底层是由数组+链表构成,数组被分成一个个桶(bucket),通过哈希值决定了键值对在这个数组中的位置。哈希值相同的键值对,会以链表形式进行存储。每一个键值对会以一个Entry实例进行封装,内部存在四个......
  • 还不懂 ConcurrentHashMap ?这份源码分析了解一下
    1.源码分析在JDK8中的ConcurrentHashMap一共有5个构造方法,这几个构造方法中都没有对内部的数组做初始化,只是对一些变量的初始值做了处理,其中ConcurrentHashMap的数组初始化是在第一次添加元素时完成的。//没有维护任何变量的操作,如果调用该方法,数组长度默认是16public C......
  • ConcurrentHashMap源码剖析
    ConcurrentHashMap源码剖析https://www.bilibili.com/video/BV1Qg41197FG/?spm_id_from=333.337.search-card.all.click&vd_source=273847a809b909b44923e3af1a7ef0b1ConcurrentHashMap是Hashmap的并发形式。虽然Hashtable也是线程安全的,但是它的并发能力相比于ConcurrentHashMa......
  • 源码解析之为何要用ConcurrentHashMap
    为什么要用ConcurrentHashMap?ConcurrentHashMap是JUC包下的一个线程安全的HashMap类,我们都知道多线程的场景下要用ConcurrentHashMap来代替HashMap使用,有没有想过为什么不能用HashMap,为什么能用ConcurrentHashMap呢?下面我通过走源码的方式,带大家看一看其中的一些细节!HashMapmap......
  • Hashmap 和 hashtable ConcurrentHashMap 区别
    HashMap和HashTable的区别:HashMap是非线程安全的,HashTable是线程安全的。HashMap的键和值都允许有null值存在,而HashTable则不行。HashMap线程不安全,HashTable线程安全,但是因为线程安全的原因,HashMap效率更高。HashTable是同步的,HashMap不是。因此,HashMap更适合于单线程环境,而H......