前言
ConcurrentHashMap 博大精深,從他的 50 多個內部類就能看出來,似乎 JDK 的並發精髓都在里面了。但他依然擁有體驗良好的 API 給我們使用,程序員根本感覺不到他內部的復雜。但,他內部的每一個方法都復雜無比,就連 size 方法,都挺復雜的。
今天就一起來看看這個 size 方法。
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 方法是核心。
sumCount 方法實現
代碼如下:
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
參數。