java.util.ConcurrentHashMap (JDK 1.8)【转】

1.1 java.util.ConcurrentHashMap继承结构

 

ConcurrentHashMap和HashMap的实现有很大的相似性,建议先看HashMap源码,再来理解ConcurrentHashMap。

1.2 java.util.ConcurrentHashMap属性

这里仅展示几个关键的属性

复制代码
 1     // ConcurrentHashMap核心数组
 2     transient volatile Node<K,V>[] table;  3  4 // 扩容时才会用的一个临时数组  5 private transient volatile Node<K,V>[] nextTable;  6  7 /**  8  * table初始化和resize控制字段  9  * 负数表示table正在初始化或resize。-1表示正在初始化,-N表示有N-1个线程正在resize操作 10  * 当table为null的时候,保存初始化表的大小以用于创建时使用,或者直接采用默认值0 11  * table初始化之后,保存下一次扩容的的大小,跟HashMap的threshold = loadFactor*capacity作用相同 12 */ 13 private transient volatile int sizeCtl; 14 15 // resize的时候下一个需要处理的元素下标为index=transferIndex-1 16 private transient volatile int transferIndex; 17 18 // 通过CAS无锁更新,ConcurrentHashMap元素总数,但不是准确值 19 // 因为多个线程同时更新会导致部分线程更新失败,失败时会将元素数目变化存储在counterCells中 20 private transient volatile long baseCount; 21 22 // resize或者创建CounterCells时的一个标志位 23 private transient volatile int cellsBusy; 24 25 // 用于存储元素变动 26 private transient volatile CounterCell[] counterCells;
复制代码

1.3 java.util.ConcurrentHashMap方法

1.3.1 Unsafe.compareAndSwapXXX方法

Unsafe.compareAndSwapXXX方法是sun.misc.Unsafe类中的方法,因为在ConcurrentHashMap中大量使用了这些方法。其声明如下:

public final native boolean compareAndSwapXXX(type1 object, type2 offset, type4 expect, type5 update);

object为待修改的对象,offset为偏移量(数组可以理解为下标),expect为期望值,update为更新值。这个方法执行的逻辑伪代码如下:

1 if (object[offset].value equal expect) {
2     object[offset].value = update;
3 return true; 4 } else { 5 return false 6 }

object[offset].value 等于expect更新value值并返回true,否则不更新并且返回false。之所以不更新是因为多线程执行时有其它线程已经修改该值,expect已经不是最新的值,如果强行修改必然会覆盖之前的修改,造成脏数据。

CAS方法都是native方法,可以保证原子性,并且效率比synchronized高。 

1.3.2 hash方法

1     static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash 2 int hash = spread(key.hashCode()); 3 4 static final int spread(int h) { 5 return (h ^ (h >>> 16)) & HASH_BITS; 6 }

上面源码为计算hash算法,h ^ (h >>> 16)在计算hash的时候key.hashCode()的高位也参与运算,这部分跟HashMap计算方法一致,不同的是h ^ (h >>> 16)计算结果“与”上 0x7fffffff,从而保证结果一定为正整数。获得hash之后,通过hash & (n -1)计算下标。 

ConcurrentHashMap中的元素节点总结一下有这么几种可能:

(1) null 暂无元素

(2) Node<K, V> 普通节点,可以组成单向链表,hash > 0

(3) TreeBin<K,V> 红黑树节点,TreeBin是对TreeNode的封装,其hash为TREEBIN = -2。

HashMap和ConcurrentHashMap的TreeNode实现并不相同。

在HashMap中TreeNode封装了红黑树所有的操作方法,而ConcurrentHashMap中红黑树操作的方法都封装在TreeBin中,TreeBin相当于一个红黑树容器,容器中的红黑树节点为TreeNode。

HashMap可以直接在tab[i]存入TreeNode,而ConCurrentHashMap只能在tab[i]存入TreeBin。

(4) ForwardingNode<K,V> key和value都为null的一个特殊节点,用于resize操作填充已经完成迁移操作的节点。FrowardingNode的hash在初始化的时候被置成MOVED = -1

在resize过程中当发现tab[i]上是ForwardingNode的时候(通过hash判断)就可知tab[i]已经迁移完了,直接跳过该节点去处理其它节点。

ConcurrentHashMap禁止node的key或value为null或许跟该节点的存在也是有一定关系的。

(5)ReservationNode<K,V>只在compute和computeIfAbsent中使用,其hash为RESERVED = -3

从上面的总结可以看出普通节点hash为正整数是有意义的,hash > 0是判断该节点是否为链表节点(普通节点)的一个重要依据。

1.3.3 get/set/update tab[i] 方法

复制代码
 1     // 获取tab[i]节点
 2     static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {  3 return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);  4  }  5  6 // compare and swap tab[i],期望值是c,tab[i].value == c ? tab[i] = v : return false  7 static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {  8 return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);  9  } 10 11 // 设置tab[i] = v 12 static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) { 13 U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); 14 }
复制代码

1.3.4 size() 方法

复制代码
 1     public int size() {
 2         long n = sumCount();  3 return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n);  4  }  5  6 final long sumCount() {  7 CounterCell[] as = counterCells; CounterCell a;  8 long sum = baseCount;  9 // 除了baseCount以外,部分元素变化存储在counterCells数组中 10 if (as != null) { 11 // 遍历数组累加获得结果 12 for (int i = 0; i < as.length; ++i) { 13 if ((a = as[i]) != null) 14 sum += a.value; 15  } 16  } 17 return sum; 18 }
复制代码

ConcurrentHashMap中baseCount用于保存tab中元素总数,但是并不准确,因为多线程同时增删改,会导致baseCount修改失败,此时会将元素变动存储于counterCells数组内。

当需要统计当前的size的时候,除了要统计baseCount之外,还需要统计counterCells中的元素变化。

值得一提的是即使如此,统计出来的依旧不是当前tab中元素的准确值,在多线程环境下统计前后并不能stop the world暂停线程操作,因此无法保证准确性。

1.3.5 put/putIfAbsent方法

复制代码
 1     public V put(K key, V value) {
 2         // 核心是调用putVal方法  3 return putVal(key, value, false);  4  }  5  6 public V putIfAbsent(K key, V value) {  7 // 如果key存在就不更新value  8 return putVal(key, value, true);  9  } 10 11 /** Implementation for put and putIfAbsent */ 12 final V putVal(K key, V value, boolean onlyIfAbsent) { 13 // key或value 为null都是不允许的,因为Forwarding Node就是key和value都为null,是用作标志位的。 14 if (key == null || value == null) throw new NullPointerException(); 15 // 根据key计算hash值,有了hash就可以计算下标了 16 int hash = spread(key.hashCode()); 17 int binCount = 0; 18 // 可能需要初始化或扩容,因此一次未必能完成插入操作,所以添加上for循环 19 for (Node<K,V>[] tab = table;;) { 20 Node<K,V> f; int n, i, fh; 21 // 表还没有初始化,先初始化,lazily initialized 22 if (tab == null || (n = tab.length) == 0) 23 tab = initTable(); 24 // 根据hash计算应该插入的index,该位置上还没有元素,则直接插入 25 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 26 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) 27 break; // no lock when adding to empty bin 28  } 29 // static final int MOVED = -1; // hash for forwarding nodes 30 // 说明f为ForwardingNode,只有扩容的时候才会有ForwardingNode出现在tab中,因此可以断定该tab正在进行扩容 31 else if ((fh = f.hash) == MOVED) 32 // 协助扩容 33 tab = helpTransfer(tab, f); 34 else { 35 V oldVal = null; 36 // 节点上锁,hash值相同的节点组成的链表头结点 37 synchronized (f) { 38 if (tabAt(tab, i) == f) { 39 if (fh >= 0) { // 是链表节点 40 binCount = 1; 41 for (Node<K,V> e = f;; ++binCount) { 42  K ek; 43 // 遍历链表查找是否包含该元素 44 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { 45 oldVal = e.val; // 保存旧的值用于当做返回值 46 if (!onlyIfAbsent) 47 e.val = value; // 替换旧的值为新值 48 break; 49  } 50 Node<K,V> pred = e; 51 if ((e = e.next) == null) { 52 // 遍历链表,如果一直没找到,则新建一个Node放到链表结尾 53 pred.next = new Node<K,V>(hash, key, value, null); 54 break; 55  } 56  } 57  } 58 else if (f instanceof TreeBin) { // 是红黑树节点 59 Node<K,V> p; 60 binCount = 2; 61 // 去红黑树查找该元素,如果没找到就添加,找到了就返回该节点 62 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { 63 // 保存旧的value用于返回 64 oldVal = p.val; 65 if (!onlyIfAbsent) 66 p.val = value; // 替换旧的值 67  } 68  } 69  } 70  } 71 if (binCount != 0) { 72 if (binCount >= TREEIFY_THRESHOLD) 73 // 链表长度超过阈值(默认为8),则需要将链表转为一棵红黑树 74  treeifyBin(tab, i); 75 if (oldVal != null) 76 // 如果只是替换,并未带来节点的增加则直接返回旧的value即可 77 return oldVal; 78 break; 79  } 80  } 81  } 82 // 元素总数加1,并且判断是否需要扩容 83 addCount(1L, binCount); 84 return null; 85 }
复制代码

1.3.6 addCount方法

在putVal方法中调用了若干其它方法,下面来看下addCount方法。

复制代码
 1     // check<0不检查resize, check<=1只在没有线程竞争的情况下检查resize
 2     private final void addCount(long x, int check) {  3 CounterCell[] as; long b, s;  4 // counterCells数组不为null  5 if ((as = counterCells) != null ||  6 // CAS更新BASECOUNT失败(有其它线程更新了BASECOUNT,baseCount已经不是最新值)  7 !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {  8 CounterCell a; long v; int m;  9 boolean uncontended = true; 10 // counterCells为null 11 if (as == null || (m = as.length - 1) < 0 || 12 // counterCells对应位置为null,这里不是很懂,有没有大神解答下? 13 // ThreadLocalRandom.getProbe() 获得线程探测值,什么用途? 14 (a = as[ThreadLocalRandom.getProbe() & m]) == null || 15 // 更新CELLVALUE失败 16 !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { 17 // 初始化counterCells 18  fullAddCount(x, uncontended); 19 return; 20  } 21 // counterCells != null 或者 BASECOUNT CAS更新失败都是因为有线程竞争,因此不检查resize 22 if (check <= 1) 23 return; 24 // 统计下ConcurrentHashMap元素总数 25 s = sumCount(); 26  } 27 if (check >= 0) { 28 Node<K,V>[] tab, nt; int n, sc; 29 // 元素总数大于sizeCtl 30 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { 31 // 获取一个resize标志位 32 int rs = resizeStamp(n); 33 // sizeCtl < 0 表示table正在初始化或者resize 34 if (sc < 0) { 35 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 36 sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) 37 break; 38 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) 39  transfer(tab, nt); 40  } 41 // 当前线程是第一个发起扩容操作 42 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) 43 transfer(tab, null); 44 s = sumCount(); 45  } 46  } 47 }
复制代码

1.3.7 resize相关方法:resizeStamp、helpTransfer、transfer

复制代码
1     // 返回一个标志位,该标志位经过RESIZE_STAMP_SHIFT左移必定为负数
2     static final int resizeStamp(int n) { 3 // Integer.numberOfLeadingZeros返回n对应32位二进制数左侧0的个数,如9(1001)返回28 4 // 1 << (RESIZE_STAMP_BITS - 1) = 2^15,其中RESIZE_STAMP_BITS固定为16 5 return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)); 6  } 7 
复制代码

helpTransfer方法:辅助扩容方法,直接进入transfer方法的迁移元素阶段

复制代码
 1     final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
 2         Node<K,V>[] nextTab; int sc;  3 // 在tab中发现了ForwardingNode,在ForwardingNode初始化的时候保存了nextTable引用  4 // 因此可以通过f找到nextTable,并且可以断定nextTable!=null  5 if (tab != null && (f instanceof ForwardingNode) &&  6 (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {  7 int rs = resizeStamp(tab.length);  8 while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {  9 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 10 sc == rs + MAX_RESIZERS || transferIndex <= 0) 11 break; 12 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { 13  transfer(tab, nextTab); 14 break; 15  } 16  } 17 return nextTab; 18  } 19 return table; 20 } 
复制代码

transfer方法:resize的核心操作。基本思路是先new一个double capacity的nextTable数组,然后将tab中的元素一个一个迁移到nextTable中。迁移完成后将tab = nextTable操作替换掉tab。

复制代码
  1     // 扩容操作方法
  2     private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {  3 int n = tab.length, stride;  4 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)  5 stride = MIN_TRANSFER_STRIDE; // subdivide range  6 // 刚开始resize,需要初始化nextTab  7 if (nextTab == null) {  8 try {  9 @SuppressWarnings("unchecked")  10 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 扩容为两倍  11 nextTab = nt;  12 } catch (Throwable ex) { // try to cope with OOME  13 sizeCtl = Integer.MAX_VALUE;  14 return;  15  }  16 nextTable = nextTab;  17 transferIndex = n; // 倒序transfer tab  18  }  19 int nextn = nextTab.length; // 扩容后表的length  20 // 预先定义一个头节点ForwardingNode,其hash被置成MOVED=-1  21 // 当线程发现某个元素hash==MOVED则表明该节点已经被处理过  22 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);  23 boolean advance = true;  24 // 是否完成元素迁移的标志  25 boolean finishing = false; // to ensure sweep before committing nextTab  26 for (int i = 0, bound = 0;;) {  27 Node<K,V> f; int fh;  28 // 这个while循环是为了找到下一个准备处理的下标  29 while (advance) {  30 int nextIndex, nextBound;  31 // --i还未越界,准备处理tab[i]  32 // finishing==true,resize完成,可能处于提交前的检查阶段,检查tab[--i]  33 if (--i >= bound || finishing)  34 advance = false;  35 // 下一个准备处理的元素下标为transferIndex-1<0, 可以断定tab已经完成了transfer操作  36 else if ((nextIndex = transferIndex) <= 0) {  37 i = -1;  38 advance = false;  39  }  40 else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,  41 nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {  42 bound = nextBound;  43 i = nextIndex - 1; // 下一个准备处理的index  44 advance = false;  45  }  46  }  47 // i越界,可能已经完成元素迁移操作  48 if (i < 0 || i >= n || i + n >= nextn) {  49 int sc;  50 if (finishing) { // 扩容完成,替换table  51 // 扩容完成nextTable置空  52 nextTable = null;  53 // 替换table为扩容后的nextTab  54 table = nextTab;  55 // sizeCtl设置为0.75 * capacity,即为下一次需要扩容的阈值  56 sizeCtl = (n << 1) - (n >>> 1);  57 return;  58  }  59 // CAS更新sizeCtl,sc-1表示新加入一个线程参与扩容操作  60 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {  61 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)  62 return;  63 finishing = advance = true;  64 // 处理完成后重新遍历一遍,以免多线程操作带来遗漏  65 i = n; // recheck before commit  66  }  67  }  68 else if ((f = tabAt(tab, i)) == null)  69 // tab[i] == null则置一个ForwardingNode  70 advance = casTabAt(tab, i, null, fwd);  71 else if ((fh = f.hash) == MOVED)  72 // ForwardingNode的hash为MOVED,说明tab[i]已经被置成ForwardingNode,已经处理过  73 advance = true; // already processed  74 else {  75 // 对tab[i]节点加锁,锁住了tab[i]节点上所有的Node  76 synchronized (f) {  77 // 如果AB两个线程先后执行到这里,A线程获取锁,执行完迁移之后释放锁;B线程获取锁,此时tab[i]是ForwardingNode,不等于f  78 if (tabAt(tab, i) == f) {  79 Node<K,V> ln, hn;  80 // fh >= 0说明是链表节点。TreeBin的hash在初始化的时候被置成TREEBIN=-2  81 if (fh >= 0) {  82 // (fh = f.hash) & n 决定Node应该迁移到原下标i还是应该迁移到i+n位置  83 // 这种扩容方法参考HashMap的resize思想 http://www.cnblogs.com/snowater/p/7742287.html  84 int runBit = fh & n;  85 Node<K,V> lastRun = f;  86 // 遍历链表找到最后一个与链表头结点runBit不同的Node,并且将runBit置为该节点的 p.hash & n  87 for (Node<K,V> p = f.next; p != null; p = p.next) {  88 int b = p.hash & n;  89 if (b != runBit) {  90 runBit = b;  91 lastRun = p;  92  }  93  }  94 if (runBit == 0) {  95 // runBit == 0 表明该Node还应迁移到下标i的位置  96 ln = lastRun;  97 hn = null;  98  }  99 else { 100 // runBit != 0 表明该Node应迁移到下标i + n的位置 101 hn = lastRun; 102 ln = null; 103  } 104 // 遍历链表,拆分之,拆分后基本是原链表的倒序(最后一段链表除外,它还是以顺序的方式处于链表末尾) 105 for (Node<K,V> p = f; p != lastRun; p = p.next) { 106 int ph = p.hash; K pk = p.key; V pv = p.val; 107 if ((ph & n) == 0) // 该Node应该迁移到下标i位置 108 ln = new Node<K,V>(ph, pk, pv, ln); 109 else // 该Node应该迁移到下标i+n位置 110 hn = new Node<K,V>(ph, pk, pv, hn); 111  } 112  setTabAt(nextTab, i, ln); 113 setTabAt(nextTab, i + n, hn); 114 // 处理完后将tab[i]设置为ForwardingNode,其它线程发现tab[i] == ForwardingNode则会跳过tab[i]继续往后执行 115  setTabAt(tab, i, fwd); 116 advance = true; 117  } 118 else if (f instanceof TreeBin) { // TreeBin的hash为-2 119 TreeBin<K,V> t = (TreeBin<K,V>)f; 120 TreeNode<K,V> lo = null, loTail = null; 121 TreeNode<K,V> hi = null, hiTail = null; 122 int lc = 0, hc = 0; 123 for (Node<K,V> e = t.first; e != null; e = e.next) { 124 int h = e.hash; 125 TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null); 126 if ((h & n) == 0) { 127 if ((p.prev = loTail) == null) 128 lo = p; 129 else 130 loTail.next = p; 131 loTail = p; 132 ++lc; 133 } else { 134 if ((p.prev = hiTail) == null) 135 hi = p; 136 else 137 hiTail.next = p; 138 hiTail = p; 139 ++hc; 140  } 141  } 142 // 如果长度小于UNTREEIFY_THRESHOLD=8,则将树转换为链表,否则将lo和hi重建为红黑树 143 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t; 144 hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t; 145  setTabAt(nextTab, i, ln); 146 setTabAt(nextTab, i + n, hn); 147  setTabAt(tab, i, fwd); 148 advance = true; 149  } 150  } 151  } 152  } 153  } 154 } 
复制代码

1.3.8 get方法

复制代码
 1     public V get(Object key) {
 2         Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;  3 // 根据key计算得到hash  4 int h = spread(key.hashCode());  5 if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {  6 if ((eh = e.hash) == h) {  7 if ((ek = e.key) == key || (ek != null && key.equals(ek)))  8 return e.val;  9  } 10 else if (eh < 0) // 红黑树,从红黑树中查找 11 return (p = e.find(h, key)) != null ? p.val : null; 12 // 遍历链表查找 13 while ((e = e.next) != null) { 14 if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) 15 return e.val; 16  } 17  } 18 return null; 19 }
复制代码

参考博客:

http://www.importnew.com/23610.html
http://blog.csdn.net/u010723709/article/details/48007881
http://blog.csdn.net/qq924862077/article/details/74530103
http://www.cnblogs.com/mickole/articles/3757278.html
http://www.techsite.cn/?p=5520
http://blog.csdn.net/dfdsggdgg/article/details/51538601

原文:https://www.cnblogs.com/snowater/p/8087166.html

猜你喜欢

转载自www.cnblogs.com/makai/p/12689031.html
今日推荐