ConcurrentHashmap中的size()方法簡單解釋
阿新 • • 發佈:2019-01-31
本文所有的原始碼都是基於JDK1.8
ConcurrentHashmap中的size()方法原始碼:
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
根據JDK1.8的註解,當你想大致瞭解ConcurrentHashmap的容器大小時,建議使用mappingCount()方法。原始碼如下:
/**
* Returns the number of mappings. This method should be used
* instead of {@link #size} because a ConcurrentHashMap may
* contain more mappings than can be represented as an int. The
* value returned is an estimate; the actual count may differ if
* there are concurrent insertions or removals.
*(大致的意思是:返回容器的大小。這個方法應該被用來代替size()方法,因為
* ConcurrentHashMap的容量大小可能會大於int的最大值。
* 返回的值是一個估計值;如果有併發插入或者刪除操作,則實際的數量可能有所不同。)
* @return the number of mappings
* @since 1.8
*/
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}
其實baseCount就是記錄容器數量的,直接放回baseCount不就可以了嗎?為什麼sumCount()方法中還要遍歷counterCells陣列,累加物件的值呢?
其中:counterCells是個全域性的變數,表示的是CounterCell類陣列。CounterCell是ConcurrentHashmap的內部類,它就是儲存一個值。
/**
* Table of counter cells. When non-null, size is a power of 2.
*/
private transient volatile CounterCell[] counterCells;
/**
* A padded cell for distributing counts. Adapted from LongAdder
* and Striped64. See their internal docs for explanation.
*/
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
JDK1.8中使用一個volatile型別的變數baseCount記錄元素的個數,當插入新資料put()或則刪除資料remove()時,會通過addCount()方法更新baseCount:
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
//U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 每次竟來都baseCount都加1因為x=1
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//1
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
//多執行緒CAS發生失敗的時候執行
fullAddCount(x, uncontended);//2
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
//當條件滿足開始擴容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {//如果小於0說明已經有執行緒在進行擴容操作了
//一下的情況說明已經有在擴容或者多執行緒進行了擴容,其他執行緒直接break不要進入擴容操作
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))//如果相等說明擴容已經完成,可以繼續擴容
transfer(tab, nt);
}
//這個時候sizeCtl已經等於(rs << RESIZE_STAMP_SHIFT) + 2等於一個大的負數,這邊加上2很巧妙,因為transfer後面對sizeCtl--操作的時候,最多隻能減兩次就結束
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
1、初始化時counterCells為空,在併發量很高時,如果存在兩個執行緒同時執行CAS修改baseCount值,則失敗的執行緒會繼續執行方法體中的邏輯,執行fullAddCount(x, uncontended)方法,這個方法其實就是初始化counterCells,並將x的值插入到counterCell類中,而x值一般也就是1,這單可以從put()方法中得知。
final V putVal(K key, V value, boolean onlyIfAbsent) {
...
//1就是要CAS 更新baseCount的值,binCount代表此連結串列或樹的值,一般都大於0.
addCount(1L, binCount);
return null;
}
所以counterCells儲存的都是value為1的CounterCell物件,而這些物件是因為在CAS更新baseCounter值時,由於高併發而導致失敗,最終將值儲存到CounterCell中,放到counterCells裡。這也就是為什麼sumCount()中需要遍歷counterCells陣列,sum累加CounterCell.value值了。