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是更好的选择。