ConcurrentHashMap实现机制


转载自http://www.iteye.com/topic/1103980

ConcurrentHashMap

ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和HashTable功能相同但是线程安全的方法。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。

ConcurrentHashMap的内部结构

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组,我们用下面这一幅图来看下ConcurrentHashMap的内部结构:
图表1
从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

Segment

我们再来具体了解一下Segment的数据结构:

 

Java代码   收藏代码
  1. static final class Segment<K,V> extends ReentrantLock implements Serializable {  
  2.     transient volatile int count;  
  3.     transient int modCount;  
  4.     transient int threshold;  
  5.     transient volatile HashEntry<K,V>[] table;  
  6.     final float loadFactor;  
  7. }  
 

 

详细解释一下Segment里面的成员变量的意义:

  • count:Segment中元素的数量
  • modCount:对table的大小造成影响的操作的数量(比如put或者remove操作)
  • threshold:阈值,Segment里面元素的数量超过这个值依旧就会对Segment进行扩容
  • table:链表数组,数组中的每一个元素代表了一个链表的头部
  • loadFactor:负载因子,用于确定threshold
HashEntry

Segment中的元素是以HashEntry的形式存放在链表数组中的,看一下HashEntry的结构:

 

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

可以看到HashEntry的一个特点,除了value以外,其他的几个变量都是final的,这样做是为了防止链表结构被破坏,出现ConcurrentModification的情况。

ConcurrentHashMap的初始化

下面我们来结合源代码来具体分析一下ConcurrentHashMap的实现,先看下初始化方法:

 

Java代码   收藏代码
  1. public ConcurrentHashMap(int initialCapacity,  
  2.                          float loadFactor, int concurrencyLevel) {  
  3.     if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)  
  4.         throw new IllegalArgumentException();  
  5.    
  6.     if (concurrencyLevel > MAX_SEGMENTS)  
  7.         concurrencyLevel = MAX_SEGMENTS;  
  8.    
  9.     // Find power-of-two sizes best matching arguments  
  10.     int sshift = 0;  
  11.     int ssize = 1;  
  12.     while (ssize < concurrencyLevel) {  
  13.         ++sshift;  
  14.         ssize <<= 1;  
  15.     }  
  16.     segmentShift = 32 - sshift;  
  17.     segmentMask = ssize - 1;  
  18.     this.segments = Segment.newArray(ssize);  
  19.    
  20.     if (initialCapacity > MAXIMUM_CAPACITY)  
  21.         initialCapacity = MAXIMUM_CAPACITY;  
  22.     int c = initialCapacity / ssize;  
  23.     if (c * ssize < initialCapacity)  
  24.         ++c;  
  25.     int cap = 1;  
  26.     while (cap < c)  
  27.         cap <<= 1;  
  28.    
  29.     for (int i = 0; i < this.segments.length; ++i)  
  30.         this.segments[i] = new Segment<K,V>(cap, loadFactor);  
  31. }  
 

CurrentHashMap的初始化一共有三个参数,一个initialCapacity,表示初始的容量,一个loadFactor,表示负载参数,最后一个是concurrentLevel,代表ConcurrentHashMap内部的Segment的数量,ConcurrentLevel一经指定,不可改变,后续如果ConcurrentHashMap的元素数量增加导致ConrruentHashMap需要扩容,ConcurrentHashMap不会增加Segment的数量,而只会增加Segment中链表数组的容量大小,这样的好处是扩容过程不需要对整个ConcurrentHashMap做rehash,而只需要对Segment里面的元素做一次rehash就可以了。

整个ConcurrentHashMap的初始化方法还是非常简单的,先是根据concurrentLevel来new出Segment,这里Segment的数量是不大于concurrentLevel的最大的2的指数,就是说Segment的数量永远是2的指数个,这样的好处是方便采用移位操作来进行hash,加快hash的过程。接下来就是根据intialCapacity确定Segment的容量的大小,每一个Segment的容量大小也是2的指数,同样使为了加快hash的过程。

这边需要特别注意一下两个变量,分别是segmentShift和segmentMask,这两个变量在后面将会起到很大的作用,假设构造函数确定了Segment的数量是2的n次方,那么segmentShift就等于32减去n,而segmentMask就等于2的n次方减一。

ConcurrentHashMap的get操作

前面提到过ConcurrentHashMap的get操作是不用加锁的,我们这里看一下其实现:

 

Java代码   收藏代码
  1. public V get(Object key) {  
  2.     int hash = hash(key.hashCode());  
  3.     return segmentFor(hash).get(key, hash);  
  4. }  
 

看第三行,segmentFor这个函数用于确定操作应该在哪一个segment中进行,几乎对ConcurrentHashMap的所有操作都需要用到这个函数,我们看下这个函数的实现:

 

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

这个函数用了位操作来确定Segment,根据传入的hash值向右无符号右移segmentShift位,然后和segmentMask进行与操作,结合我们之前说的segmentShift和segmentMask的值,就可以得出以下结论:假设Segment的数量是2的n次方,根据元素的hash值的高n位就可以确定元素到底在哪一个Segment中。

在确定了需要在哪一个segment中进行操作以后,接下来的事情就是调用对应的Segment的get方法:

 

Java代码   收藏代码
  1. V get(Object key, int hash) {  
  2.     if (count != 0) { // read-volatile  
  3.         HashEntry<K,V> e = getFirst(hash);  
  4.         while (e != null) {  
  5.             if (e.hash == hash && key.equals(e.key)) {  
  6.                 V v = e.value;  
  7.                 if (v != null)  
  8.                     return v;  
  9.                 return readValueUnderLock(e); // recheck  
  10.             }  
  11.             e = e.next;  
  12.         }  
  13.     }  
  14.     return null;  
  15. }  
 

先看第二行代码,这里对count进行了一次判断,其中count表示Segment中元素的数量,我们可以来看一下count的定义:

 

Java代码   收藏代码
  1. transient volatile int count;  
 

可以看到count是volatile的,实际上这里里面利用了volatile的语义:

 

 写道
对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。

因为实际上put、remove等操作也会更新count的值,所以当竞争发生的时候,volatile的语义可以保证写操作在读操作之前,也就保证了写操作对后续的读操作都是可见的,这样后面get的后续操作就可以拿到完整的元素内容。

然后,在第三行,调用了getFirst()来取得链表的头部:

 

Java代码   收藏代码
  1. HashEntry<K,V> getFirst(int hash) {  
  2.     HashEntry<K,V>[] tab = table;  
  3.     return tab[hash & (tab.length - 1)];  
  4. }  
 

同样,这里也是用位操作来确定链表的头部,hash值和HashTable的长度减一做与操作,最后的结果就是hash值的低n位,其中n是HashTable的长度以2为底的结果。

在确定了链表的头部以后,就可以对整个链表进行遍历,看第4行,取出key对应的value的值,如果拿出的value的值是null,则可能这个key,value对正在put的过程中,如果出现这种情况,那么就加锁来保证取出的value是完整的,如果不是null,则直接返回value。

ConcurrentHashMap的put操作

看完了get操作,再看下put操作,put操作的前面也是确定Segment的过程,这里不再赘述,直接看关键的segment的put方法:

 

Java代码   收藏代码
  1. V put(K key, int hash, V value, boolean onlyIfAbsent) {  
  2.     lock();  
  3.     try {  
  4.         int c = count;  
  5.         if (c++ > threshold) // ensure capacity  
  6.             rehash();  
  7.         HashEntry<K,V>[] tab = table;  
  8.         int index = hash & (tab.length - 1);  
  9.         HashEntry<K,V> first = tab[index];  
  10.         HashEntry<K,V> e = first;  
  11.         while (e != null && (e.hash != hash || !key.equals(e.key)))  
  12.             e = e.next;  
  13.    
  14.         V oldValue;  
  15.         if (e != null) {  
  16.             oldValue = e.value;  
  17.             if (!onlyIfAbsent)  
  18.                 e.value = value;  
  19.         }  
  20.         else {  
  21.             oldValue = null;  
  22.             ++modCount;  
  23.             tab[index] = new HashEntry<K,V>(key, hash, first, value);  
  24.             count = c; // write-volatile  
  25.         }  
  26.         return oldValue;  
  27.     } finally {  
  28.         unlock();  
  29.     }  
  30. }  
 

首先对Segment的put操作是加锁完成的,然后在第五行,如果Segment中元素的数量超过了阈值(由构造函数中的loadFactor算出)这需要进行对Segment扩容,并且要进行rehash,关于rehash的过程大家可以自己去了解,这里不详细讲了。

第8和第9行的操作就是getFirst的过程,确定链表头部的位置。

第11行这里的这个while循环是在链表中寻找和要put的元素相同key的元素,如果找到,就直接更新更新key的value,如果没有找到,则进入21行这里,生成一个新的HashEntry并且把它加到整个Segment的头部,然后再更新count的值。

ConcurrentHashMap的remove操作

Remove操作的前面一部分和前面的get和put操作一样,都是定位Segment的过程,然后再调用Segment的remove方法:

 

Java代码   收藏代码
  1. V remove(Object key, int hash, Object value) {  
  2.     lock();  
  3.     try {  
  4.         int c = count - 1;  
  5.         HashEntry<K,V>[] tab = table;  
  6.         int index = hash & (tab.length - 1);  
  7.         HashEntry<K,V> first = tab[index];  
  8.         HashEntry<K,V> e = first;  
  9.         while (e != null && (e.hash != hash || !key.equals(e.key)))  
  10.             e = e.next;  
  11.    
  12.         V oldValue = null;  
  13.         if (e != null) {  
  14.             V v = e.value;  
  15.             if (value == null || value.equals(v)) {  
  16.                 oldValue = v;  
  17.                 // All entries following removed node can stay  
  18.                 // in list, but all preceding ones need to be  
  19.                 // cloned.  
  20.                 ++modCount;  
  21.                 HashEntry<K,V> newFirst = e.next;  
  22.                 for (HashEntry<K,V> p = first; p != e; p = p.next)  
  23.                     newFirst = new HashEntry<K,V>(p.key, p.hash,  
  24.                                                   newFirst, p.value);  
  25.                 tab[index] = newFirst;  
  26.                 count = c; // write-volatile  
  27.             }  
  28.         }  
  29.         return oldValue;  
  30.     } finally {  
  31.         unlock();  
  32.     }  
  33. }  
 

首先remove操作也是确定需要删除的元素的位置,不过这里删除元素的方法不是简单地把待删除元素的前面的一个元素的next指向后面一个就完事了,我们之前已经说过HashEntry中的next是final的,一经赋值以后就不可修改,在定位到待删除元素的位置以后,程序就将待删除元素前面的那一些元素全部复制一遍,然后再一个一个重新接到链表上去,看一下下面这一幅图来了解这个过程:
1
假设链表中原来的元素如上图所示,现在要删除元素3,那么删除元素3以后的链表就如下图所示:
2

ConcurrentHashMap的size操作

在前面的章节中,我们涉及到的操作都是在单个Segment中进行的,但是ConcurrentHashMap有一些操作是在多个Segment中进行,比如size操作,ConcurrentHashMap的size操作也采用了一种比较巧的方式,来尽量避免对所有的Segment都加锁。

前面我们提到了一个Segment中的有一个modCount变量,代表的是对Segment中元素的数量造成影响的操作的次数,这个值只增不减,size操作就是遍历了两次Segment,每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同,则把这个过程再重复做一次,如果再不相同,则就需要将所有的Segment都锁住,然后一个一个遍历了,具体的实现大家可以看ConcurrentHashMap的源码,这里就不贴了。


ConcurrentHashMap源码实现 转载自http://www.iteye.com/topic/977348

ConcurrentHashMap类

   研究源码时,我一般喜欢从实际的应用中去一步步调试分析。。这样理解起来容易很多。

 

   实际应用:

 

Java代码   收藏代码
  1.  ConcurrentMap<String, String> map = new ConcurrentHashMap<String, String>();  
  2. String oldValue = map.put("zhxing""value");  
  3. String oldValue1 = map.put("zhxing""value1");  
  4. String oldValue2 = map.putIfAbsent("zhxing""value2");  
  5. String value = map.get("zhxing");  
  6.   
  7. System.out.println("oldValue:" + oldValue);  
  8. System.out.println("oldValue1:" + oldValue1);  
  9. System.out.println("oldValue2:" + oldValue2);  
  10. System.out.println("value:" + value);  
 

输出结果:

Java代码   收藏代码
  1. oldValue:null  
  2. oldValue1:value  
  3. oldValue2:value1  
  4. value:value1  
 

先从new 方法开始

Java代码   收藏代码
  1.     /** 
  2.      * Creates a new, empty map with a default initial capacity (16), load 
  3.      * factor (0.75) and concurrencyLevel(也就是锁的个数) (16). 
  4.      *  
  5.      */  
  6. public ConcurrentHashMap() {  
  7.         this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);  
  8.     }  
  9. // 当都是默认的设置参数  
  10.     public ConcurrentHashMap(int initialCapacity, float loadFactor,  
  11.             int concurrencyLevel) {  
  12.         if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)  
  13.             throw new IllegalArgumentException();  
  14.         // MAX_SEGMENTS = 1 << 16,锁的个数有限制  
  15.         if (concurrencyLevel > MAX_SEGMENTS)  
  16.             concurrencyLevel = MAX_SEGMENTS;  
  17.   
  18.         // Find power-of-two sizes best matching arguments  
  19.         // 这里是根据设定的并发数查找最优的并发数  
  20.         int sshift = 0;  
  21.         int ssize = 1;  
  22.         while (ssize < concurrencyLevel) {  
  23.             ++sshift;  
  24.             ssize <<= 1;// 不断右移  
  25.         }  
  26.         // 到这里,sshift=4,ssize=16.因为concurrencyLevel=16=1<<4  
  27.         segmentShift = 32 - sshift;// =16  
  28.         segmentMask = ssize - 1;// =3  
  29.         // 创建了16个分段(Segment),其实每个分段相当于一个带锁的map  
  30.         this.segments = Segment.newArray(ssize);  
  31.   
  32.         if (initialCapacity > MAXIMUM_CAPACITY)  
  33.             initialCapacity = MAXIMUM_CAPACITY;  
  34.         // 这里是计算每个分段存储的容量  
  35.         int c = initialCapacity / ssize;// c=16/16=1  
  36.         if (c * ssize < initialCapacity)// 防止分段的相加的容量小于总容量  
  37.             ++c;  
  38.         int cap = 1;  
  39.         // 如果初始容量比cap的容量小,则已双倍的容量增加  
  40.         while (cap < c)  
  41.             cap <<= 1;  
  42.         // 分别new分段  
  43.         for (int i = 0; i < this.segments.length; ++i)  
  44.             this.segments[i] = new Segment<K, V>(cap, loadFactor);  
  45.     }  
 

这里提到了一个Segment 这个类,其实这个是总map 的分段,就是为了实现分段锁机制。

Java代码   收藏代码
  1. /** 
  2.      * Segments are specialized versions of hash tables. This subclasses from 
  3.      * ReentrantLock opportunistically, just to simplify some locking and avoid 
  4.      * separate construction. map 的分段实现,扩展了锁机制 
  5.      */  
  6.     static final class Segment<K, V> extends ReentrantLock implements  
  7.             Serializable {  
  8. //。。。  
  9. Segment(int initialCapacity, float lf) {  
  10.             loadFactor = lf;  
  11.             // 这个是开始初始化map容器了  
  12.             setTable(HashEntry.<K, V> newArray(initialCapacity));  
  13.         }  
  14.         /** 
  15.          * Sets table to new HashEntry array. Call only while holding lock or in 
  16.          * constructor. 
  17.          */  
  18.         void setTable(HashEntry<K, V>[] newTable) {  
  19.             threshold = (int) (newTable.length * loadFactor);  
  20.             table = newTable;  
  21.         }  
  22. }  
  23.   
  24.     // 这个是实际保存到map的东西了,如果对HashMap源码有了解的话,是不是觉得很像Hash.Entry,但又没实现Map.Entry接口,它是用另外个类WriteThroughEntry  
  25.     // 来实现这个Map.Entry接口的。  
  26.     static final class HashEntry<K, V> {  
  27.         final K key;  
  28.         final int hash;  
  29.         volatile V value;  
  30.         final HashEntry<K, V> next;  
  31.   
  32.         HashEntry(K key, int hash, HashEntry<K, V> next, V value) {  
  33.             this.key = key;  
  34.             this.hash = hash;  
  35.             this.next = next;  
  36.             this.value = value;  
  37.         }  
  38.   
  39.         @SuppressWarnings("unchecked")  
  40.         // 新建数组,保存着map里的键值对  
  41.         static final <K, V> HashEntry<K, V>[] newArray(int i) {  
  42.             return new HashEntry[i];  
  43.         }  
 

get方法实现

Java代码   收藏代码
  1. //ConcurrentHashMap类      
  2. // 在这里发现,get操作几乎是不带锁的。。效率提高很多  
  3.     public V get(Object key) {  
  4.         // key不能为null 。。  
  5.         int hash = hash(key); // throws NullPointerException if key null  
  6.         return segmentFor(hash).get(key, hash);  
  7.     }  
  8.   
  9.     // 这个hash方式不太懂,估计是为了能均匀分布吧  
  10.     static int hash(Object x) {  
  11.         int h = x.hashCode();  
  12.         h += ~(h << 9);  
  13.         h ^= (h >>> 14);  
  14.         h += (h << 4);  
  15.         h ^= (h >>> 10);  
  16.         return h;  
  17.     }  
  18.   
  19.     /** 
  20.      * Returns the segment that should be used for key with given hash 这个是寻找所在分段 
  21.      *  
  22.      * @param hash 
  23.      *            the hash code for the key 
  24.      * @return the segment 
  25.      */  
  26.     final Segment<K, V> segmentFor(int hash) {  
  27.         // hash>>>16&3  
  28.         return segments[(hash >>> segmentShift) & segmentMask];  
  29.     }  
  30.   
  31. //Segment 类方法  
  32.         /* Specialized implementations of map methods */  
  33.         // 获得值了,和其他map的get的实现其实差不多  
  34.         V get(Object key, int hash) {  
  35.             // count 是每个分段的键值对个数,而且是volatile,保证在内存中只有一份  
  36.             if (count != 0) { // read-volatile  
  37.                 // 获得分段中hash链表的第一个值  
  38.                 HashEntry<K, V> e = getFirst(hash);  
  39.                 while (e != null) {  
  40.                     if (e.hash == hash && key.equals(e.key)) {  
  41.                         V v = e.value;  
  42.                         if (v != null)  
  43.                             return v;  
  44.                         // 这个做了一个挺有趣的检查,如果v==null,而key!=null,的时候会等待锁中value的值  
  45.                         return readValueUnderLock(e); // recheck  
  46.                     }  
  47.                     e = e.next;  
  48.                 }  
  49.             }  
  50.             return null;  
  51.         }  
  52.   
  53.         /** 
  54.          * Reads value field of an entry under lock. Called if value field ever 
  55.          * appears to be null. This is possible only if a compiler happens to 
  56.          * reorder a HashEntry initialization with its table assignment, which 
  57.          * is legal under memory model but is not known to ever occur. 
  58.          */  
  59.         V readValueUnderLock(HashEntry<K, V> e) {  
  60.             lock();  
  61.             try {  
  62.                 return e.value;  
  63.             } finally {  
  64.                 unlock();  
  65.             }  
  66.         }  

 

put 方法

Java代码   收藏代码
  1. //ConcurrentHashMap类  
  2.     // 注意的是key 和value 都不能为空  
  3.     public V put(K key, V value) {  
  4.         if (value == null)  
  5.             throw new NullPointerException();  
  6.         // 和get方式一样的hash 方式  
  7.         int hash = hash(key);  
  8.         return segmentFor(hash).put(key, hash, value, false);  
  9.     }  
  10.   
  11. //Segment 类  
  12.   
  13.     V put(K key, int hash, V value, boolean onlyIfAbsent) {  
  14.             // 这里加锁了  
  15.             lock();  
  16.             try {  
  17.                 int c = count;  
  18.                 // 如果超过限制,就重新分配  
  19.                 if (c++ > threshold) // ensure capacity  
  20.                     rehash();  
  21.                 HashEntry<K, V>[] tab = table;  
  22.                 int index = hash & (tab.length - 1);  
  23.                 HashEntry<K, V> first = tab[index];  
  24.                 HashEntry<K, V> e = first;  
  25.                 // e的值总是在链表的最后一个  
  26.                 while (e != null && (e.hash != hash || !key.equals(e.key)))  
  27.                     e = e.next;  
  28.   
  29.                 V oldValue;  
  30.                 if (e != null) {  
  31.                     oldValue = e.value;  
  32.                     // 这里就是实现putIfAbsent 的方式  
  33.                     if (!onlyIfAbsent)  
  34.                         e.value = value;  
  35.                 } else {  
  36.                     oldValue = null;  
  37.                     ++modCount;  
  38.                     tab[index] = new HashEntry<K, V>(key, hash, first, value);  
  39.                     count = c; // write-volatile  
  40.                 }  
  41.                 return oldValue;  
  42.             } finally {  
  43.                 unlock();  
  44.             }  
  45.         }  
  46.   
  47.         // 这中扩容方式应该和其他map 的扩容一样  
  48.         void rehash() {  
  49.             HashEntry<K, V>[] oldTable = table;  
  50.             int oldCapacity = oldTable.length;  
  51.             // 如果到了最大容量则不能再扩容了,max=1<<30,这将可能导致的一个后果是map的操作越来越慢  
  52.             if (oldCapacity >= MAXIMUM_CAPACITY)  
  53.                 return;  
  54.   
  55.             /* 
  56.              * Reclassify nodes in each list to new Map. Because we are using 
  57.              * power-of-two expansion, the elements from each bin must either 
  58.              * stay at same index, or move with a power of two offset. We 
  59.              * eliminate unnecessary node creation by catching cases where old 
  60.              * nodes can be reused because their next fields won't change. 
  61.              * Statistically, at the default threshold, only about one-sixth of 
  62.              * them need cloning when a table doubles. The nodes they replace 
  63.              * will be garbage collectable as soon as they are no longer 
  64.              * referenced by any reader thread that may be in the midst of 
  65.              * traversing table right now. 
  66.              */  
  67.             // 以两倍的方式增长  
  68.             HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);  
  69.             threshold = (int) (newTable.length * loadFactor);  
  70.             int sizeMask = newTable.length - 1;  
  71.             // 下面的数据拷贝就没多少好讲的了  
  72.             for (int i = 0; i < oldCapacity; i++) {  
  73.                 // We need to guarantee that any existing reads of old Map can  
  74.                 // proceed. So we cannot yet null out each bin.  
  75.                 HashEntry<K, V> e = oldTable[i];  
  76.   
  77.                 if (e != null) {  
  78.                     HashEntry<K, V> next = e.next;  
  79.                     int idx = e.hash & sizeMask;  
  80.   
  81.                     // Single node on list  
  82.                     if (next == null)  
  83.                         newTable[idx] = e;  
  84.   
  85.                     else {  
  86.                         // Reuse trailing consecutive sequence at same slot  
  87.                         HashEntry<K, V> lastRun = e;  
  88.                         int lastIdx = idx;  
  89.                         for (HashEntry<K, V> last = next; last != null; last = last.next) {  
  90.                             int k = last.hash & sizeMask;  
  91.                             if (k != lastIdx) {  
  92.                                 lastIdx = k;  
  93.                                 lastRun = last;  
  94.                             }  
  95.                         }  
  96.                         newTable[lastIdx] = lastRun;  
  97.   
  98.                         // Clone all remaining nodes  
  99.                         for (HashEntry<K, V> p = e; p != lastRun; p = p.next) {  
  100.                             int k = p.hash & sizeMask;  
  101.                             HashEntry<K, V> n = newTable[k];  
  102.                             newTable[k] = new HashEntry<K, V>(p.key, p.hash, n,  
  103.                                     p.value);  
  104.                         }  
  105.                     }  
  106.                 }  
  107.             }  
  108.             table = newTable;  
  109.         }  

 

size 方法

Java代码   收藏代码
  1. /** 
  2.  * Returns the number of key-value mappings in this map. If the map contains 
  3.  * more than <tt>Integer.MAX_VALUE</tt> elements, returns 
  4.  * <tt>Integer.MAX_VALUE</tt>. javadoc 上也写明了,返回的数值不能超过Int的最大值,超过也返回最大值 
  5.  * 在下面的分析也可以看出,为了减少锁竞争做了一些性能优化,这种的优化方式在很多方法都有使用 
  6.  *  
  7.  * @return the number of key-value mappings in this map 
  8.  */  
  9. public int size() {  
  10.     final Segment<K, V>[] segments = this.segments;  
  11.     long sum = 0;  
  12.     long check = 0;  
  13.     int[] mc = new int[segments.length];  
  14.     // Try a few times to get accurate count. On failure due to  
  15.     // continuous async changes in table, resort to locking.  
  16.     // 这里最多试RETRIES_BEFORE_LOCK=2 次的检查对比  
  17.     for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {  
  18.         check = 0;  
  19.         sum = 0;// size 总数  
  20.         int mcsum = 0;// 修改的总次数  
  21.         // 这里保存了一份对比值,供下次对比时使用  
  22.         for (int i = 0; i < segments.length; ++i) {  
  23.             sum += segments[i].count;  
  24.             mcsum += mc[i] = segments[i].modCount;  
  25.         }  
  26.         // 只有当map初始化的时候才等于0  
  27.         if (mcsum != 0) {  
  28.             // 在此对比上面保存的修改值  
  29.             for (int i = 0; i < segments.length; ++i) {  
  30.                 check += segments[i].count;  
  31.                 if (mc[i] != segments[i].modCount) {  
  32.                     check = -1// force retry  
  33.                     break;  
  34.                 }  
  35.             }  
  36.         }  
  37.         // 检查和第一次保存值一样则结束循环  
  38.         if (check == sum)  
  39.             break;  
  40.     }  
  41.     // 当不相等的时候,这里就只有用锁来保证正确性了  
  42.     if (check != sum) { // Resort to locking all segments  
  43.         sum = 0;  
  44.         for (int i = 0; i < segments.length; ++i)  
  45.             segments[i].lock();  
  46.         for (int i = 0; i < segments.length; ++i)  
  47.             sum += segments[i].count;  
  48.         for (int i = 0; i < segments.length; ++i)  
  49.             segments[i].unlock();  
  50.     }  
  51.     // 这里也可以看出,如果超过int 的最大值值返回int 最大值  
  52.     if (sum > Integer.MAX_VALUE)  
  53.         return Integer.MAX_VALUE;  
  54.     else  
  55.         return (int) sum;  
  56. }  

 

keys 方法

Java代码   收藏代码
  1.     public Enumeration<K> keys() {  
  2.         //这里新建了一个内部Iteraotr 类  
  3.         return new KeyIterator();  
  4.     }  
  5. //这里主要是继承了HashIterator 方法,基本的实现都在HashIterator 中  
  6.     final class KeyIterator extends HashIterator implements Iterator<K>,  
  7.             Enumeration<K> {  
  8.         public K next() {  
  9.             return super.nextEntry().key;  
  10.         }  
  11.   
  12.         public K nextElement() {  
  13.             return super.nextEntry().key;  
  14.         }  
  15.     }  
  16.   
  17.     /* ---------------- Iterator Support -------------- */  
  18.     // 分析代码发现,这个遍历过程没有涉及到锁,查看Javadoc 后可知该视图的 iterator 是一个“弱一致”的迭代器。。  
  19.     abstract class HashIterator {  
  20.         int nextSegmentIndex;// 下一个分段的index  
  21.         int nextTableIndex;// 下一个分段的容器的index  
  22.         HashEntry<K, V>[] currentTable;// 当前容器  
  23.         HashEntry<K, V> nextEntry;// 下个键值对  
  24.         HashEntry<K, V> lastReturned;// 上次返回的键值对  
  25.   
  26.         HashIterator() {  
  27.             nextSegmentIndex = segments.length - 1;  
  28.             nextTableIndex = -1;  
  29.             advance();  
  30.         }  
  31.   
  32.         public boolean hasMoreElements() {  
  33.             return hasNext();  
  34.         }  
  35.   
  36.         // 先变量键值对的链表,再对table 数组的index 遍历,最后遍历分段数组的index。。这样就可以完整的变量完所有的entry了  
  37.         final void advance() {  
  38.             // 先变量键值对的链表  
  39.             if (nextEntry != null && (nextEntry = nextEntry.next) != null)  
  40.                 return;  
  41.             // 对table 数组的index 遍历  
  42.             while (nextTableIndex >= 0) {  
  43.                 if ((nextEntry = currentTable[nextTableIndex--]) != null)  
  44.                     return;  
  45.             }  
  46.             // 遍历分段数组的index  
  47.             while (nextSegmentIndex >= 0) {  
  48.                 Segment<K, V> seg = segments[nextSegmentIndex--];  
  49.                 if (seg.count != 0) {  
  50.                     currentTable = seg.table;  
  51.                     for (int j = currentTable.length - 1; j >= 0; --j) {  
  52.                         if ((nextEntry = currentTable[j]) != null) {  
  53.                             nextTableIndex = j - 1;  
  54.                             return;  
  55.                         }  
  56.                     }  
  57.                 }  
  58.             }  
  59.         }  
  60.   
  61.         public boolean hasNext() {  
  62.             return nextEntry != null;  
  63.         }  
  64.   
  65.         HashEntry<K, V> nextEntry() {  
  66.             if (nextEntry == null)  
  67.                 throw new NoSuchElementException();  
  68.             // 把上次的entry换成当前的entry  
  69.             lastReturned = nextEntry;  
  70.             // 这里做一些预操作  
  71.             advance();  
  72.             return lastReturned;  
  73.         }  
  74.   
  75.         public void remove() {  
  76.             if (lastReturned == null)  
  77.                 throw new IllegalStateException();  
  78.             ConcurrentHashMap.this.remove(lastReturned.key);  
  79.             lastReturned = null;  
  80.         }  
  81.     }  

 

keySet/Values/elements 这几个方法都和keys 方法非常相似。。就不解释了。。而entrySet 方法有点特别。。我也有点不是很明白。。

Java代码   收藏代码
  1. //这里没什么好说的,看下就明白,主要在下面  
  2. public Set<Map.Entry<K, V>> entrySet() {  
  3.         Set<Map.Entry<K, V>> es = entrySet;  
  4.         return (es != null) ? es : (entrySet = new EntrySet());  
  5.     }  
  6.   
  7.     final class EntrySet extends AbstractSet<Map.Entry<K, V>> {  
  8.         public Iterator<Map.Entry<K, V>> iterator() {  
  9.             return new EntryIterator();  
  10.         }  
  11. }  
  12. //主要在这里,新建了一个WriteThroughEntry 这个类  
  13.     final class EntryIterator extends HashIterator implements  
  14.             Iterator<Entry<K, V>> {  
  15.         public Map.Entry<K, V> next() {  
  16.             HashEntry<K, V> e = super.nextEntry();  
  17.             return new WriteThroughEntry(e.key, e.value);  
  18.         }  
  19.     }  
  20.   
  21.     /** 
  22.      * Custom Entry class used by EntryIterator.next(), that relays setValue 
  23.      * changes to the underlying map. 
  24.      * 这个主要是返回一个Entry,但有点不明白的是为什么不在HashEntry中实现Map 
  25.      * .Entry就可以了(HashMap就是这样的),为了减少锁竞争?? 
  26.      */  
  27.     final class WriteThroughEntry extends AbstractMap.SimpleEntry<K, V> {  
  28.         WriteThroughEntry(K k, V v) {  
  29.             super(k, v);  
  30.         }  
  31.   
  32.         /** 
  33.          * Set our entry's value and write through to the map. The value to 
  34.          * return is somewhat arbitrary here. Since a WriteThroughEntry does not 
  35.          * necessarily track asynchronous changes, the most recent "previous" 
  36.          * value could be different from what we return (or could even have been 
  37.          * removed in which case the put will re-establish). We do not and 
  38.          * cannot guarantee more. 
  39.          */  
  40.         public V setValue(V value) {  
  41.             if (value == null)  
  42.                 throw new NullPointerException();  
  43.             V v = super.setValue(value);  
  44.             ConcurrentHashMap.this.put(getKey(), value);  
  45.             return v;  
  46.         }  
  47.     }  
 

   从上面可以看出,ConcurrentHash 也没什么特别的,大概的思路就是采用分段锁机制来实现的,把之前用一个容易EntryTable来装的转换成多个Table来装键值对。而方法里面的也采用了不少为了减少锁竞争而做的一些优化。。从ConcurrentHash类里面可以看出,它里面实现了一大堆的内部类。。比如Segment/KeyIterator/ValueIterator/EntryIterator等等。。个人觉得有些代码好像比较难理解。。比如Segment 类继承ReentrantLock,为什么不用组合呢。。还会有上面提到的,HashEntry 为什么不像HashMap 的Entry一样实现Map.Entry接口。。建立这么多内部类,搞得人头晕晕的。。。。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值