ConcurrentHashMap是JDK1.5并发包中提供的线程安全的HashMap的实现,其包结构关系如下:

Java代码  

1. public class ConcurrentHashMap<K, V> extends
2. implements
3. }  
4. public abstract class AbstractMap<K,V> implements
5. }  
6. public interface ConcurrentMap<K, V> extends
7. }

 ConcurrentHashMap实现并发是通过“锁分离”技术来实现的,也就是将锁拆分,不同的元素拥有不同的 锁,ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,其中每一个片段是一个类似于HashMap的结构,它有一个 HashEntry的数组,数组的每一项又是一个链表,通过HashEntry的next引用串联起来,它们有自己的锁。

Java代码  

1. final

 Segment继承自ReentrantLock,在创建Segment对象时,其所做的动作就是创建一个指定大小为cap的HashEntry 对象数组,并基于数组的大小及loadFactor计算threshold的值:threshold = (int)(newTable.length * loadFactor);

Java代码  

1. Segment(int initialCapacity, float
2.     loadFactor = lf;  
3.     setTable(HashEntry.<K,V>newArray(initialCapacity));  
4. }  
5. void
6. int)(newTable.length * loadFactor);  
7.     table = newTable;  
8. }

 

构造函数

Java代码  

1. public ConcurrentHashMap(int
2. float
3. int
4.   
5. 参数:  
6. initialCapacity - 初始容量。该实现执行内部大小调整,以容纳这些元素。  
7. loadFactor - 加载因子阈值,用来控制重新调整大小。在每 bin 中的平均元素数大于此阈值时,可能要重新调整大小。  
8. concurrencyLevel - 当前更新线程的估计数。该实现将执行内部大小调整,以尽量容纳这些线程。   
9. 抛出:   
10. IllegalArgumentException - 如果初始容量为负,或者加载因子或 concurrencyLevel 为非正。  
11.   
12. public ConcurrentHashMap(int
13. float loadFactor, int
14. if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)  
15. throw new
16.   
17. if
18.             concurrencyLevel = MAX_SEGMENTS;  
19.   
20. // Find power-of-two sizes best matching arguments
21. int sshift = 0;  
22. int ssize = 1;  
23. while
24.             ++sshift;  
25. 1;  
26.         }  
27. 32
28. 1;  
29. this.segments = Segment.newArray(ssize);  
30.   
31. if
32.             initialCapacity = MAXIMUM_CAPACITY;  
33. int
34. if
35.             ++c;  
36. int cap = 1;  
37. while
38. 1;  
39.   
40. for (int i = 0; i < this.segments.length; ++i)  
41. this.segments[i] = new
42.     }

  

 基于如下方法计算ssize的大小:

Java代码  

1. int sshift = 0;  
2. int ssize = 1;  
3. while
4.     ++sshift;  
5. 1;  
6. }

 默认情况下构造函数的三个值分别为16、0.75f、16。在concurrencyLevel为16的情况下,计算出的ssize值为16,并 使用该值作为参数传入Senment的newArray方法创建一个大小为16的Segment对象数组,也就是默认情况下 ConcurrentHashMap是用了16个类似HashMap 的结构。

采用下面方法计算cap变量的值:

Java代码  

1. int
2. if
3.     ++c;  
4. int cap = 1;  
5. while
6. 1;

 算出的cap为1。

 

 put(Object key,Object value)方法

 ConcurrentHashMap的put方法并没有加synchronized来保证线程同步,而是在Segment中实现同步,如下:

 

Java代码  

1. public
2. if (value == null)  
3. throw new
4. int
5. return segmentFor(hash).put(key, hash, value, false);  
6.     }  
7.   
8. //下面为Segment的put方法
9. V put(K key, int hash, V value, boolean
10.             lock();  
11. try
12. int
13. if (c++ > threshold) // ensure capacity
14.                     rehash();  
15.                 HashEntry<K,V>[] tab = table;  
16. int index = hash & (tab.length - 1);  
17.                 HashEntry<K,V> first = tab[index];  
18.                 HashEntry<K,V> e = first;  
19. while (e != null
20.                     e = e.next;  
21.   
22.                 V oldValue;  
23. if (e != null) {  
24.                     oldValue = e.value;  
25. if
26.                         e.value = value;  
27.                 }  
28. else
29. null;  
30.                     ++modCount;  
31. new
32. // write-volatile
33.                 }  
34. return
35. finally
36.                 unlock();  
37.             }  
38.         }

 

 ConcurrentHashMap不能保存value为null值,否则抛出NullPointerException,key也不能为空:

 

 

Java代码  

1. int

从上面可以看出ConcurrentHashMap基于concurrencyLevel划分出多个Segment来存储对象,从则避免每次put操作都锁住得锁住整个数组。在默认的情况下可以充许16个线程并发无阻塞的操作集合对象。

remove(Object key)方法:

Java代码  

1. public
2.  hash = hash(key.hashCode());  
3. return segmentFor(hash).remove(key, hash, null);  
4. }

get(Object key)方法:

 跟put和remove方法一样,首先对key进行hash,再根据该hash值找到对应的Segment对象,然后调用该Segment对象的get方法完成操作。

Java代码  

1. public
2. int
3. return
4. }

Java代码  

1. V get(Object key, int
2. if (count != 0) { // read-volatile
3.         HashEntry<K,V> e = getFirst(hash);  
4. while (e != null) {  
5. if
6.                 V v = e.value;  
7. if (v != null)  
8. return
9. return readValueUnderLock(e); // recheck
10.             }  
11.             e = e.next;  
12.         }  
13.     }  
14. return null;  
15. }  
16. V readValueUnderLock(HashEntry<K,V> e) {  
17.     lock();  
18. try
19. return
20. finally
21.         unlock();  
22.     }  
23. }

  

get方法首先 通过hash值和HashEntry对象数组大小减1按位与来获取对应位置的HashEntry,在这个步骤中可能因为数组大小的改变而导致获取 HashEntry数组对象位置出错,ConcurrentHashMap通过把HashEntry数组对象定义为volatile类型来保证线程同步。

Java代码  

1. transient volatile
2. HashEntry<K,V> getFirst(int
3.     HashEntry<K,V>[] tab = table;  
4. return tab[hash & (tab.length - 1)];  
5. }

 和HashMap性能比较

在单线程情况 下,ConcurrentHashMap比HashMap性能稍微差一点,在多线程情况下,随着线程数量的增加,ConcurrentHashMap性能 明显比HashMap提升,特别是查找性能,而且随着线程数量的增加,ConcurrentHashMap性能并没有出现下降的情况,所以在并发的场景 中,使用ConcurrentHashMap比使用HashMap是更好的选择。