深入理解 ConcurrentHashMap

转自:深入理解 ConcurrentHashMap

前言

HashMap是我们平时开发过程中用的比较多的集合,但它是非线程安全的,在涉及到多线程并发的情况,进行put操作有可能会引起死循环,导致CPU利用率接近100%。

1
2
3
4
5
6
7
8
9
final HashMap<String, String> map = new HashMap<String, String>(2);
for (int i = 0; i < 10000; i++) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            map.put(UUID.randomUUID().toString(), "");
        }
    }).start();
}

解决方案有Hashtable和Collections.synchronizedMap(hashMap),不过这两个方案基本上是对读写进行加锁操作,一个线程在读写元素,其余线程必须等待,性能可想而知。

所以,Doug Lea给我们带来了并发安全的ConcurrentHashMap,ConcurrentHashMap是Java1.5中引用的一个线程安全的支持高并发的HashMap集合类。

HashTable与ConcurrentHashMap的对比

HashTable本身是线程安全的,写过Java程序的都知道通过加Synchronized关键字实现线程安全,这样对整张表加锁实现同步的一个缺陷就在于使程序的效率变得很低。这就是为什么Java中会在1.5后引入ConcurrentHashMap的原因。

从图中可以看出,HashTable的锁加在整个Hash表上,而ConcurrentHashMap将锁加在segment上(每个段上),这样我们在对segment1操作的时候,同时也可以对segment2中的数据操作,这样效率就会高很多。

ConcurrentHashMap JDK1.6分析

ConcurrentHashMap采用分段锁的机制,实现并发的更新操作,底层采用数组+链表+红黑树的存储结构。

ConcurrentHashMap主要有三大结构:整个Hash表,segment(段),HashEntry(节点)。每个segment就相当于一个HashTable。

一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组,下面我们通过一个图来演示一下 ConcurrentHashMap 的结构:

HashEntry类

每个HashEntry代表Hash表中的一个节点,在其定义的结构中可以看到,除了value值没有定义final,其余的都定义为final类型,我们知道Java中关键词final修饰的域成为最终域。用关键词final修饰的变量一旦赋值,就不能改变,也称为修饰的标识为常量。这就意味着我们删除或者增加一个节点的时候,就必须从头开始重新建立Hash链,因为next引用值需要改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
static final class HashEntry<K,V> { 
        final K key;                 // 声明 key 为 final 型
        final int hash;              // 声明 hash 值为 final 型 
        volatile V value;           // 声明 value 为 volatile 型
        final HashEntry<K,V> next;  // 声明 next 为 final 型 
 
        HashEntry(K key, int hash, HashEntry<K,V> next, V value)  { 
            this.key = key; 
            this.hash = hash; 
            this.next = next; 
            this.value = value; 
        
 }

由于这样的特性,所以插入Hash链中的数据都是从头开始插入的。例如将A,B,C插入空桶中,插入后的结构为:

segment类

Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。

table 是一个由 HashEntry 对象组成的数组。table 数组的每一个数组成员就是散列映射表的一个桶。

count 变量是一个计数器,它表示每个 Segment 对象管理的 table 数组(若干个 HashEntry 组成的链表)包含的 HashEntry 对象的个数。每一个 Segment 对象都有一个 count 对象来表示本 Segment 中包含的 HashEntry 对象的总数。注意,之所以在每个 Segment 对象中包含一个计数器,而不是在 ConcurrentHashMap 中使用全局的计数器,是为了避免出现“热点域”而影响 ConcurrentHashMap 的并发性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static final class Segment<K,V> extends ReentrantLock implements Serializable {  
 private static final long serialVersionUID = 2249069246763182397L;  
         /** 
          * 在本 segment 范围内,包含的 HashEntry 元素的个数
          * 该变量被声明为 volatile 型,保证每次读取到最新的数据
          */  
         transient volatile int count;  
 
         /** 
          *table 被更新的次数
          */  
         transient int modCount;  
 
         /** 
          * 当 table 中包含的 HashEntry 元素的个数超过本变量值时,触发 table 的再散列
          */  
         transient int threshold;  
 
         /** 
          * table 是由 HashEntry 对象组成的数组
          * 如果散列时发生碰撞,碰撞的 HashEntry 对象就以链表的形式链接成一个链表
          * table 数组的数组成员代表散列映射表的一个桶
          * 每个 table 守护整个 ConcurrentHashMap 包含桶总数的一部分
          * 如果并发级别为 16,table 则守护 ConcurrentHashMap 包含的桶总数的 1/16 
          */  
         transient volatile HashEntry<K,V>[] table;  
 
         /** 
          * 装载因子
          */  
         final float loadFactor;  
 }

ConcurrentHashMap 类

默认的情况下,每个ConcurrentHashMap 类会创建16个并发的segment,每个segment里面包含多个Hash表,每个Hash链都是有HashEntry节点组成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>  
         implements ConcurrentMap<K, V>, Serializable {  
     /** 
      * segments 的掩码值
      * key 的散列码的高位用来选择具体的 segment  
      */  
     final int segmentMask;  
 
     /** 
      * 偏移量
      */  
     final int segmentShift;  
 
     /** 
      * 由 Segment 对象组成的数组,每个都是一个特别的Hash Table
      */  
     final Segment<K,V>[] segments;  
 }

ConcurrentHashMap JDK1.8分析

1.8的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构。

Reference:

http://blog.csdn.net/dingji_ping/article/details/51005799

http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/

http://javabypatel.blogspot.jp/2016/09/concurrenthashmap-interview-questions.html

http://www.jianshu.com/p/c0642afe03e0

转载请并标注: “本文转载自 linkedkeeper.com (文/张松然)”

此条目发表在java分类目录,贴了标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注