一.背景
前文讲了HashMap的源码分析,从中可以看到下面的问题:
- HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低下;
- 在遍历迭代获取时进行修改(put/remove)操作,会导致发生并发修改异常(ConcurrentModificationException);
- 在JDK1.7之前,对HashMap进行put添加操作,会导致链表反转,造成链表回路,从而发生get死循环,(当然这个问题在JDK1.8被改进了按照原链表顺序进行重排移动);
- 如果多个线程同时检测到元素个数超过 数组大小 * loadFactor,这样就会发生多个线程同时对数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失;
基于上述问题,都可以使用ConcurrentHashMap进行解决,ConcurrentHashMap使用分段锁技术解决了并发访问效率,在遍历迭代获取时进行修改操作也不会发生并发修改异常等等问题。
二.源码解析
-
构造方法:
//最大容量大小 private static final int MAXIMUM_CAPACITY = 1 << 30; //默认容量大小 private static final int DEFAULT_CAPACITY = 16; /** *控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义 * 多线程之间,以volatile方式读取sizeCtl属性,来判断ConcurrentHashMap当前所处的状态。 * 通过cas设置sizeCtl属性,告知其他线程ConcurrentHashMap的状态变更 *未初始化: * sizeCtl=0:表示没有指定初始容量。 * sizeCtl>0:表示初始容量。 *初始化中: * sizeCtl=-1,标记作用,告知其他线程,正在初始化 *正常状态: * sizeCtl=0.75n ,扩容阈值 *扩容中: * sizeCtl < 0 : 表示有其他线程正在执行扩容 * sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 :表示此时只有一个线程在执行扩容 */ private transient volatile int sizeCtl; //并发级别 private static final int DEFAULT_CONCURRENCY_LEVEL = 16; //创建一个新的空map,默认大小是16 public ConcurrentHashMap() { } public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); //调整table的大小,tableSizeFor的实现查看前文HashMap源码分析的构造方法模块 int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap; } public ConcurrentHashMap(Map<? extends K, ? extends V> m) { this.sizeCtl = DEFAULT_CAPACITY; putAll(m); } public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1); } /** * concurrencyLevel:并发度,预估同时操作数据的线程数量 * 表示能够同时更新ConccurentHashMap且不产生锁竞争的最大线程数。 * 默认值为16,(即允许16个线程并发可能不会产生竞争)。 */ public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); //至少使用同样多的桶容纳同样多的更新线程来操作元素 if (initialCapacity < concurrencyLevel) // Use at least as many bins initialCapacity = concurrencyLevel; // as estimated threads long size = (long)(1.0 + (long)initialCapacity / loadFactor); int cap = (size >= (long)MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)size); this.sizeCtl = cap; } 复制代码
-
put:
public V put(K key, V value) { return putVal(key, value, false); } static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash普通节点哈希的可用位 //把位数控制在int最大整数之内,h ^ (h >>> 16)的含义查看前文的put源码解析 static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; } final V putVal(K key, V value, boolean onlyIfAbsent) { //key和value为空抛出异常 if (key == null || value == null) throw new NullPointerException(); //得到hash值 int hash = spread(key.hashCode()); int binCount = 0; //自旋对table进行遍历 for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //初始化table if (tab == null || (n = tab.length) == 0) tab = initTable(); //如果hash计算出的槽位元素为null,CAS将元素填充进当前槽位并结束遍历 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //hash为-1,说明正在扩容,那么就帮助其扩容。以加快速度 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (f) {// 同步 f 节点,防止增加链表的时候导致链表成环 if (tabAt(tab, i) == f) {// 如果对应的下标位置的节点没有改变 if (fh >= 0) {//f节点的hash值大于0 binCount = 1;//链表初始长度 // 死循环,直到将值添加到链表尾部,并计算链表的长度 for (Node<K,V> e = f;; ++binCount) { K ek; //hash和key相同,值进行覆盖 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; //hash和key不同,添加到链表后面 if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //是树节点,添加到树中 else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } //如果节点添加到链表和树中 if (binCount != 0) { //链表长度大于等于8时,将链表转换成红黑树树 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } // 判断是否需要扩容 addCount(1L, binCount); return null; } 复制代码
-
initTable:初始化
private final Node<K,
-