ConcurrentHashMap 博大精深,從他的 50 多個內部類就能看出來,彷佛 JDK 的併發精髓都在裏面了。但他依然擁有體驗良好的 API 給咱們使用,程序員根本感受不到他內部的複雜。但,他內部的每個方法都複雜無比,就連 size 方法,都挺複雜的。java
今天就一塊兒來看看這個 size 方法。程序員
代碼以下:數組
public int size() { long n = sumCount(); return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); }
最大返回 int 最大值,可是這個 Map 的長度是有可能超過 int 最大值的,因此 JDK 8 增了 mappingCount 方法。代碼以下:緩存
public long mappingCount() { long n = sumCount(); return (n < 0L) ? 0L : n; // ignore transient negative values }
相比較 size 方法,mappingCount 方法的返回值是 long 類型。因此沒必要限制最大值必須是 Integer.MAX_VALUE。而 JDK 推薦使用這個方法。但這個返回值依然不必定絕對準確。多線程
從這兩個方法中能夠看出,sumCount 方法是核心。併發
代碼以下:app
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; }
上面的方法邏輯:當 counterCells 不是 null,就遍歷元素,並和 baseCount 累加。性能
兩個屬性 : baseCount 和 counterCells。測試
先看 baseCount。線程
/** * Base counter value, used mainly when there is no contention, * but also as a fallback during table initialization * races. Updated via CAS. * 當沒有爭用時,使用這個變量計數。 */ private transient volatile long baseCount;
一個 volatile 的變量,在 addCount 方法中會使用它,而 addCount 方法在 put 結束後會調用。在 addCount 方法中,會對這個變量作 CAS 加法。
可是若是併發致使 CAS 失敗了,怎麼辦呢?使用 counterCells。
若是上面 CAS 失敗了,在 fullAddCount 方法中,會繼續死循環操做,直到成功。
而這個 CounterCell 類又是上面鬼呢?
// 一種用於分配計數的填充單元。改編自LongAdder和Striped64。請查看他們的內部文檔進行解釋。 @sun.misc.Contended static final class CounterCell { volatile long value; CounterCell(long x) { value = x; } }
使用了 @sun.misc.Contended 標記的類,內部一個 volatile 變量。註釋說,改編自LongAdder和Striped64,關於這兩個類,請看 Java8 Striped64 和 LongAdder。
而關於這個註解,有必要解釋一下。這個註解標識着這個類防止須要防止 "僞共享".
說說僞共享。引用 一下別人的說法:
避免僞共享(false sharing)。
先引用個僞共享的解釋:
緩存系統中是以緩存行(cache line)爲單位存儲的。緩存行是2的整數冪個連續字節,
通常爲32-256個字節。最多見的緩存行大小是64個字節。當多線程修改互相獨立的變量時,
若是這些變量共享同一個緩存行,就會無心中影響彼此的性能,這就是僞共享。
因此僞共享對性能危害極大。
JDK 8 版本以前沒有這個註解,Doug Lea 使用拼接來解決這個問題,把緩存行加滿,讓緩存之間的修改互不影響。
在個人機器上測試,加和不加這個註解的性能差距達到了 5 倍。
好了,關於 Size 方法就簡單介紹到這裏。總結一下:
JDK 8 推薦使用mappingCount 方法,由於這個方法的返回值是 long 類型,不會由於 size 方法是 int 類型限制最大值(size 方法是接口定義的,不能修改)。
在沒有併發的狀況下,使用一個 baseCount volatile 變量就足夠了,當併發的時候,CAS 修改 baseCount 失敗後,就會使用 CounterCell 類了,會建立一個這個對象,一般對象的 volatile value 屬性是 1。在計算 size 的時候,會將 baseCount 和 CounterCell 數組中的元素的 value 累加,獲得總的大小,但這個數字仍舊多是不許確的。
還有一個須要注意的地方就是,這個 CounterCell 類使用了 @sun.misc.Contended 註解標識,這個註解是防止僞共享的。是 1.8 新增的。使用時,須要加上 -XX:-RestrictContended
參數。