`
liujiahaogood
  • 浏览: 26153 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

ConcurrentHashMap之实现细节(转)

阅读更多

ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。在这之前我对ConcurrentHashMap只有一些肤浅的理解,仅知道它采用了多个锁,大概也足够了。但是在经过一次惨痛的面试经历之后,我觉得必须深入研究它的实现。面试中被问到读是否要加锁,因为读写会发生冲突,我说必须要加锁,我和面试官也因此发生了冲突,结果可想而知。还是闲话少说,通过仔细阅读源代码,现在总算理解ConcurrentHashMap实现机制了,其实现之精巧,令人叹服,与大家共享之。

 

 

实现原理 

 

锁分离 (Lock Stripping)

 

ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

 

有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。

 

Java代码 

 

  1. /** 
  2.  * The segments, each of which is a specialized hash table 
  3.  */  
  4. final Segment<K,V>[] segments;  

 

 

不变(Immutable)和易变(Volatile)

 

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:

 

Java代码 

 

  1. static final class HashEntry<K,V> {  
  2.     final K key;  
  3.     final int hash;  
  4.     volatile V value;  
  5.     final HashEntry<K,V> next;  
  6. }  

可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

 


其它

 

为了加快定位段以及段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。当并发级别为默认值16时,也就是段的个数,hash值的高4位决定分配在哪个段中。但是我们也不要忘记《算法导论》给我们的教训:hash槽的的个数不应该是2^n,这可能导致hash槽分配不均,这需要对hash值重新再hash一次。(这段似乎有点多余了 )

 

这是重新hash的算法,还比较复杂,我也懒得去理解了。

Java代码 

 

  1. private static int hash(int h) {  
  2.     // Spread bits to regularize both segment and index locations,  
  3.     // using variant of single-word Wang/Jenkins hash.  
  4.     h += (h <<  15) ^ 0xffffcd7d;  
  5.     h ^= (h >>> 10);  
  6.     h += (h <<   3);  
  7.     h ^= (h >>>  6);  
  8.     h += (h <<   2) + (h << 14);  
  9.     return h ^ (h >>> 16);  
  10. }  

 

这是定位段的方法:

Java代码 

 

  1. final Segment<K,V> segmentFor(int hash) {  
  2.     return segments[(hash >>> segmentShift) & segmentMask];  
  3. }  

 

 

 

数据结构

 

关于Hash表的基础数据结构,这里不想做过多的探讨。Hash表的一个很重要方面就是如何解决hash冲突,ConcurrentHashMap和HashMap使用相同的方式,都是将hash值相同的节点放在一个hash链中。与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)。下面是ConcurrentHashMap的数据成员:

 

Java代码 

 

  1. public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>  
  2.         implements ConcurrentMap<K, V>, Serializable {  
  3.     /** 
  4.      * Mask value for indexing into segments. The upper bits of a 
  5.      * key's hash code are used to choose the segment. 
  6.      */  
  7.     final int segmentMask;  
  8.   
  9.     /** 
  10.      * Shift value for indexing within segments. 
  11.      */  
  12.     final int segmentShift;  
  13.   
  14.     /** 
  15.      * The segments, each of which is a specialized hash table 
  16.      */  
  17.     final Segment<K,V>[] segments;  
  18. }  

 

所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。

 

每个Segment相当于一个子Hash表,它的数据成员如下:

Java代码 

 

  1. public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>  
  2.         implements ConcurrentMap<K, V>, Serializable {  
  3.     /** 
  4.      * Mask value for indexing into segments. The upper bits of a 
  5.      * key's hash code are used to choose the segment. 
  6.      */  
  7.     final int segmentMask;  
  8.   
  9.     /** 
  10.      * Shift value for indexing within segments. 
  11.      */  
  12.     final int segmentShift;  
  13.   
  14.     /** 
  15.      * The segments, each of which is a specialized hash table 
  16.      */  
  17.     final Segment<K,V>[] segments;  
  18. }  

 

所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。

 

每个Segment相当于一个子Hash表,它的数据成员如下:

 

Java代码 

 

  1.     static final class Segment<K,V> extends ReentrantLock implements Serializable {  
  2. private static final long serialVersionUID = 2249069246763182397L;  
  3.         /** 
  4.          * The number of elements in this segment's region. 
  5.          */  
  6.         transient volatile int count;  
  7.   
  8.         /** 
  9.          * Number of updates that alter the size of the table. This is 
  10.          * used during bulk-read methods to make sure they see a 
  11.          * consistent snapshot: If modCounts change during a traversal 
  12. font-size: 12px; color: black; padding: 0px; margin: 0px;
    分享到:
    评论

相关推荐

    ConcurrentHashMap之实现细节

    ### ConcurrentHashMap实现细节详解 #### 一、概述 `ConcurrentHashMap`是Java 5引入的一种高性能、线程安全的散列表实现。相较于传统的`HashMap`,`ConcurrentHashMap`能够支持高并发环境下的多线程读写操作。...

    ConcurrentHashMap源码剖析

    ConcurrentHashMap是Java中提供的一种高效、线程安全的哈希表实现。与传统的基于synchronized关键字实现线程安全的HashTable相比,ConcurrentHashMap通过采用锁分段技术显著提高了并发性能。本文将深入探讨...

    JDK1.8中ConcurrentHashMap中computeIfAbsent死循环bug问题

    这个问题是由ConcurrentHashMap的实现细节所引起的。 ConcurrentHashMap是一个高效的哈希表实现,它可以在高并发环境下提供高性能的数据存储和检索。但是,在JDK1.8中,ConcurrentHashMap的实现存在一个严重的bug,...

    高薪程序员面试题精讲系列49之说说ConcurrentHashMap#put方法的源码及数。。。.pdf,这是一份不错的文件

    在面试中,ConcurrentHashMap的底层原理、put方法的实现细节都是高频考点。本文将对ConcurrentHashMap#put方法的源码进行详细分析,从而帮助读者更好地理解ConcurrentHashMap的工作机理。 一、ConcurrentHashMap的...

    java7-8中的 HashMap和ConcurrentHashMap全解析.pdf

    而ConcurrentHashMap是线程安全的HashMap实现,它在Java 7中采用了分段锁(Segment)的设计,每个Segment实际上是一个小型的HashMap,通过锁来确保并发安全。put过程包括: 1. 确保Segment初始化,如果需要则创建新...

    HashTable、ConcurrentHashMap.pdf

    在Java面试中,经常会问到关于数据结构如HashTable和ConcurrentHashMap的细节,以及它们在并发编程中的使用。 最后,文档中出现了诸如“2399”、“1328”、“2645”、“2633”等数字,很可能是引用了一些代码片段或...

    Java Core Sprout:基础、并发、算法

    Java Core Sprout:一个萌芽阶段的Java核心知识库。...ConcurrentHashMap 的实现原理 如何优雅地使用和理解线程池 深入理解线程通信 一个线程召集的诡异事件 线程池中你不可错过的一些细节 『ARM包入坑指北』之队列

    使用Java并发编程实现一个简单的银行账户管理系统.txt

    接下来,我们将详细探讨此程序的设计理念、关键技术和实现细节。 #### 二、关键技术点 1. **ConcurrentHashMap的应用**: - 在Java中,`ConcurrentHashMap`是一种线程安全的哈希表,适用于多线程环境下的并发访问...

    ConcurrentHashSet-main-源码.rar

    本文将深入探讨`ConcurrentHashSet`的源码,解析其设计原理和实现细节。 首先,`ConcurrentHashSet`的核心是基于` ConcurrentHashMap `(并发哈希映射)来实现的,这使得它在多线程环境下具有高效性和线程安全性。`...

    Java中的几个HashMapConcurrentHash

    `Java中的几个HashMap ConcurrentHashMap实现分析Java开发Java经验技巧共4页.pdf.zip`这个压缩包文件很可能包含了一些深入的分析和实践案例,可以帮助你更好地理解和运用这些数据结构。在实践中不断探索和总结,是...

    java.util.concurrent系列文章(2)

    #### 三、ConcurrentHashMap 的实现细节 **1. ConcurrentHashMap 结构** - `ConcurrentHashMap` 由一个 Segment 数组和多个哈希表组成。Segment 是一种可重入锁,每个 Segment 负责维护一部分哈希表。 - 每个 ...

    实现了LRU算法的缓存

    通过对源码的阅读和分析,我们可以更深入地理解LRU缓存的工作原理和具体实现细节。为了进一步学习和应用,你可以尝试阅读源码,理解每个类和方法的作用,甚至修改和扩展这个实现以满足特定需求。

    jdk源码方法注释及实现

    - **编译器与运行时**:如`com.sun.*`和`sun.*`,虽然这些包不建议直接使用,但它们包含了JVM和编译器的相关实现细节。 **压缩包子文件的文件名称列表**:这些文件名暗示了源码的组织结构,如`launcher`可能包含...

    免费开源!!Java Core Sprout:基础、并发、算法

    常用集合 数组列表/向量 链表 哈希映射 ...ConcurrentHashMap 的实现原理 如何优雅地使用和理解线程池 深入理解线程通信 一个线程召集的诡异事件 线程池中你不可错过的一些细节 『ARM包入坑指北』之队列

    Java性能优化的45个细节

    28. **使用Java内置函数**:如Arrays.sort()、Collections.sort()等,这些内部优化过的函数通常比自定义实现更快。 29. **使用StringBuilder.append()替换StringBuffer.append()**:在单线程环境中,StringBuilder...

    kmeans聚类算法的java实现

    2. **Java实现细节**: - 数据结构:首先,需要定义一个表示数据点的类,包括数据点的坐标(在多维空间中的值)以及所属的簇。同时,还需要一个类来表示簇,存储簇内的数据点和中心。 - 加载数据:从MySQL数据库中...

    2021面试题总结数据结构篇.pdf

    具体实现细节如下: - 计算key的哈希值,并确定其在数组中的位置。 - 如果对应位置的`Node`的`hash`值与计算的哈希值相同,且key也匹配,则直接返回对应的value。 - 对于`hash`值为负的情况,分别处理: - `hash =...

Global site tag (gtag.js) - Google Analytics