ConcurrentHashMap深刻分析

##Map体系## Map类图 Hashtable是JDK 5以前Map惟一线程安全的内置实现(Collections.synchronizedMap不算)。Hashtable继承的是Dictionary(Hashtable是其惟一公开的子类),并不继承AbstractMap或者HashMap.尽管Hashtable和HashMap的结构很是相似,可是他们之间并无多大联系。 ConcurrentHashMap是HashMap的线程安全版本,ConcurrentSkipListMap是TreeMap的线程安全版本。 最终可用的线程安全版本Map实现是ConcurrentHashMap、ConcurrentSkipListMap、Hashtable、Properties四个,可是Hashtable是过期的类库,所以若是能够的应该尽量的使用ConcurrentHashMap和ConcurrentSkipListMap.html

##简介## ConcurrentHashMap 是 java.util.concurrent 包的重要成员。本文将结合 Java 内存模型,分析 JDK 源代码,探索 ConcurrentHashMap 高并发的具体实现机制。 因为 ConcurrentHashMap 的源代码实现依赖于 Java 内存模型,因此阅读本文须要读者了解 Java 内存模型。同时,ConcurrentHashMap 的源代码会涉及到散列算法和链表数据结构,因此,读者须要对散列算法和基于链表的数据结构有所了解。java

###Java 内存模型### Java 语言的内存模型由一些规则组成,这些规则肯定线程对内存的访问如何排序以及什么时候能够确保它们对线程是可见的。下面咱们将分别介绍 Java 内存模型的重排序,内存可见性和 happens-before 关系。 ####重排序#### 内存模型描述了程序的可能行为。具体的编译器实现能够产生任意它喜欢的代码 -- 只要全部执行这些代码产生的结果,可以和内存模型预测的结果保持一致。这为编译器实现者提供了很大的自由,包括操做的重排序。 编译器生成指令的次序,能够不一样于源代码所暗示的“显然”版本。重排序后的指令,对于优化执行以及成熟的全局寄存器分配算法的使用,都是大有脾益的,它使得程序在计算性能上有了很大的提高。 重排序类型包括:算法

  • 编译器生成指令的次序,能够不一样于源代码所暗示的“显然”版本。
  • 处理器能够乱序或者并行的执行指令。
  • 缓存会改变写入提交到主内存的变量的次序。 ####内存可见性#### 因为现代可共享内存的多处理器架构可能致使一个线程没法立刻(甚至永远)看到另外一个线程操做产生的结果。因此 Java 内存模型规定了 JVM 的一种最小保证:何时写入一个变量对其余线程可见。 在现代可共享内存的多处理器体系结构中每一个处理器都有本身的缓存,并周期性的与主内存协调一致。假设线程 A 写入一个变量值 V,随后另外一个线程 B 读取变量 V 的值,在下列状况下,线程 B 读取的值可能不是线程 A 写入的最新值:
  • 执行线程 A 的处理器把变量 V 缓存到寄存器中。
  • 执行线程 A 的处理器把变量 V 缓存到本身的缓存中,但尚未同步刷新到主内存中去。
  • 执行线程 B 的处理器的缓存中有变量 V 的旧值。 ####Happens-before 关系#### happens-before 关系保证:若是线程 A 与线程 B 知足 happens-before 关系,则线程 A 执行动做的结果对于线程 B 是可见的。若是两个操做未按 happens-before 排序,JVM 将能够对他们任意重排序。 下面介绍几个与理解 ConcurrentHashMap 有关的 happens-before 关系法则:
  • 程序次序法则:若是在程序中,全部动做 A 出如今动做 B 以前,则线程中的每动做 A 都 happens-before 于该线程中的每个动做 B。
  • 监视器锁法则:对一个监视器的解锁 happens-before 于每一个后续对同一监视器的加锁。
  • Volatile 变量法则:对 Volatile 域的写入操做 happens-before 于每一个后续对同一 Volatile 的读操做。
  • 传递性:若是 A happens-before 于 B,且 B happens-before C,则 A happens-before C。 ##ConcurrentHashMap结构分析## 经过ConcurrentHashMap的类图来分析ConcurrentHashMap的结构。 ConcurrentHashMap类图 ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素, 每一个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。 ConcurrentHashMap结构 ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment.HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每一个 Segment 对象守护整个散列映射表的若干个桶。每一个桶是由若干个 HashEntry 对象连接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。

##ConcurrentHashMap原理实现## ###锁分离 (Lock Stripping)### 好比HashTable是一个过期的容器类,经过使用synchronized来保证线程安全,在线程竞争激烈的状况下HashTable的效率很是低下。缘由是全部访问HashTable的线程都必须竞争同一把锁。那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不一样数据段的数据时,线程间就不会存在锁竞争,从而能够有效的提升并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。 ConcurrentHashMap内部使用段(Segment)来表示这些不一样的部分,每一个段其实就是一个小的hash table,它们有本身的锁。只要多个修改操做发生在不一样的段上,它们就能够并发进行。一样当一个线程占用锁访问其中一个段数据的时候,其余段的数据也能被其余线程访问。 ConcurrentHashMap有些方法须要跨段,好比size()和containsValue(),它们可能须要锁定整个表而而不只仅是某个段,这须要按顺序锁定全部段,操做完毕后,又按顺序释放全部段的锁。这里"按顺序"是很重要的,不然极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,而且其成员变量实际上也是final的,可是,仅仅是将数组声明为final的并不保证数组成员也是final的,这须要实现上的保证。这能够确保不会出现死锁,由于得到锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。 final Segment<K,V>[] segments; ###不变(Immutable)和易变(Volatile)### ConcurrentHashMap彻底容许多个读操做并发进行,读操做并不须要加锁。若是使用传统的技术,如HashMap中的实现,若是容许能够在hash链的中间添加或删除元素,读操做不加锁将获得不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry表明每一个hash链中的一个节点,其结构以下所示:编程

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; 
     }
  }

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

##ConcurrentHashMap具体实现## ###ConcurrentHashMap初始化### ConcurrentHashMap初始化方法是经过initialCapacity,loadFactor, concurrencyLevel几个参数来初始化segments数组,段偏移量segmentShift,段掩码segmentMask和每一个segment里的HashEntry数组,初始化segments数组。让咱们来看一下初始化segmentShift,segmentMask和segments数组的源代码。缓存

http://java.chinaitlab.com/line/914247.html http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap http://www.blogjava.net/DLevin/archive/2013/10/18/405030.html安全

相关文章
相关标签/搜索