码迷,mamicode.com
首页 > 编程语言 > 详细

2.Java集合-ConcurrentHashMap实现原理及源码分析

时间:2017-08-31 14:27:05      阅读:148      评论:0      收藏:0      [点我收藏+]

标签:extend   always   开始   可变   key   不一致   情况   hash   nts   

一、为何用ConcurrentHashMap

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

  线程不安全的HashMap

  在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap

  效率低下的HashTable

  Hashtable使用synchronized来保证线程的安全,但是在线程竞争激烈的情况下Hashtable的效率非常低下。当一个线程访问Hashtable的同步方法,其他方法访问Hashtable的同步方法时,会进入阻塞或者轮询状态。如果线程1使用put进行元素添加,线程2不但不能用put方法添加于元素同是也无法用get方法来获取元素,所以竞争越激烈效率越低。

  ConcurrentHashMap的锁分段技术

  Hashtable容器在竞争激烈的并发环境效率低下的原因是所有访问Hashtable的线程都必须竞争同一把锁,假如容器有多把锁,每一把锁用于锁住容器中一部分数据,那么多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问率,这就是ConcurrentHashMap的锁分段技术。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。

 

二、结构解析

  ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以及如何锁,可以简单理解成把一个大的Hashtable分解成多个,形成了锁分离

技术分享

  感觉一个segment 就相当于一个 Hashtable

 

二、应用场景

  当有一个大数组时需要在多个线程共享时就可以考虑是否把它给分层多个节点了,避免大锁。并可以考虑通过hash算法进行一些模块定位

  其实不止用于线程,当设计数据表的事务时(事务事务某种意义上也是同步机制的体现),可以把一个表看成一个需要同步的数组,如果操作的表数据太多就可以考虑事务分离了(这也是为什么要避免大表的出现),比如把数据进行字段拆分、水平分表等

 

三、源码解读

  从上图可以看出,ConcurrentHashMap内部分为很多个Segment,每一个Segment拥有一把锁,然后每个Segment(继承ReentrantLock)下面包含很多个HashEntry列表数据。对于一个key,需要经过三次(为什么要hash三次?后面解释)hash操作,才能最终定位这个元素的位置,这三次hash分别为:

  1.对于一个key,先进行一次hash操作,得到hash值h1,也即h1 = hash1(key)

  2.将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 = hash2(h1高几位),通过h2能够确定该元素放在哪个Segment

  3.将得到的h1进行第三次hash,得到hash值h3,也即h3=hash3(h1),确定h3能够确定该元素放置在哪个HashEntry

注:在使用key定位Segment之前进行的那次hash操作,即第一次hash, 这次hash的主要目的是为了减少哈希冲突,使元素能够均匀的分布在不同的Segment上,从而提高容器的存取效率。假如哈希的质量差到极点,那么所有的元素都在一个Segment中,不仅存取元素缓慢,分段锁也会失去意义

  ConcurrentHashMap核心方法特点阐述

  ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap的实现技术是保证 HashEntry几乎是不可变的,为了确保读操作能够看到最新的值,将value设置成volatile;;下面是HashEntry的结构

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,这避免了加锁。

  

  ConcurrentHashMap的数据成员及其作用阐述:

 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主要是为了定位段

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

  

  每个Segment相当于一个小的 Hashtable,它的数据成员如下:

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.          * of segments computing size or checking containsValue, then 
13.          * we might have an inconsistent view of state so (usually) 
14.          * must retry. 
15.          */  
16.         transient int modCount;  
17.   
18.         /** 
19.          * The table is rehashed when its size exceeds this threshold. 
20.          * (The value of this field is always <tt>(int)(capacity * 
21.          * loadFactor)</tt>.) 
22.          */  
23.         transient int threshold;  
24.   
25.         /** 
26.          * The per-segment table. 
27.          */  
28.         transient volatile HashEntry<K,V>[] table;  
29.   
30.         /** 
31.          * The load factor for the hash table.  Even though this value 
32.          * is same for all segments, it is replicated to avoid needing 
33.          * links to outer object. 
34.          * @serial 
35.          */  
36.         final float loadFactor;  
37. }

  count用来统计该段数据的个数,它是volatile,它用来协调修改和读取操作,以保证读取操作能够读取到几乎最新的修改。协调方式是这样的:每次修改操作做了结构上的改变,如增加/删除节点(修改节点的值不算结构上的改变),都要写count值,每次读取操作之前都要读取count的值

  

  

 

2.Java集合-ConcurrentHashMap实现原理及源码分析

标签:extend   always   开始   可变   key   不一致   情况   hash   nts   

原文地址:http://www.cnblogs.com/xuzekun/p/7454682.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!