#ConcurrentHashMap size 方法
面试官问:"ConcurrentHashMap 的 size() 方法是怎么实现的?"
候选人小任答不上来。
【面试官心理】 size 方法的实现是 ConcurrentHashMap 的经典设计问题。能说出 LongAdder 分段计数的候选人,说明对并发编程有深入研究。
#一、演进历史 🔴
#JDK 7:累加 Segment.count
// JDK 7 的 size 方法
public int size() {
final Segment<K, V>[] segments = this.segments;
long sum = 0;
int segcnt = segments.length;
for (int i = 0; i < segcnt; ++i) {
Segment<K, V> s = segments[i];
// 需要锁住 Segment 才能准确计数
s.lock();
try {
sum += s.count;
} finally {
s.unlock();
}
}
if (sum > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
return (int) sum;
}
// 问题:需要锁住所有 Segment,开销大
// 优化:先不加锁快速估计,如果估计值变化,再加锁精确计数#JDK 8:LongAdder 分段计数
// JDK 8 的 size 方法
public int size() {
long n = sumCount(); // 调用 sumCount
return (n < 0L) ? 0 : (n >= (long) Integer.MAX_VALUE) ?
Integer.MAX_VALUE : (int) n;
}
final long sumCount() {
CounterCell[] cells = counterCells;
long sum = baseCount; // baseCount 是基础计数
if (cells != null) {
for (CounterCell cell : cells) {
sum += cell.value; // 累加所有分段
}
}
return sum;
}#二、LongAdder 原理 🟡
// LongAdder:分段计数,减少竞争
// 比 AtomicLong 在高并发下性能更好
// AtomicLong:所有线程竞争一个值
// LongAdder:每个线程有自己的分段,累加时减少竞争
// 内部结构:
public class LongAdder extends Striped64 {
transient volatile Cell[] cells; // 分段数组
transient volatile long base; // 基础值
public void increment() {
add(1L);
}
public void add(long x) {
Cell[] cells = this.cells;
long b, v;
int m;
Cell c;
// CASE 1: cells 未初始化,直接 CAS base
if ((cells == null || (m = cells.length - 1) < 0 ||
(c = cells[Thread.currentThread().hashCode & m]) == null) {
casBase(b = baseCount, b + x); // CAS 更新 base
}
// CASE 2: cells 初始化,CAS 更新分段
else if (casBase(b = baseCount, b + x)) {
// Success
}
// CASE 3: 分段 CAS 失败,扩容分段数组
else {
// ...
}
}
}#三、size 可能不精确 🟡
// ConcurrentHashMap 的 size() 是不精确的(类似结果)
// 因为:
// 1. put 和 remove 是并发执行的
// 2. size() 在遍历累加的过程中,元素数量可能变化
// 3. 不可能获取"精确"的实时大小(除非锁住整个表)
// JDK 8 的 size():
public int size() {
long n = sumCount();
// 如果计数为负(溢出),返回 0
// 如果计数 >= MAX_VALUE,返回 MAX_VALUE
return ((n < 0L) ? 0 :
(n >= (long) Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int) n);
}
// 建议:如果需要精确计数,使用 AtomicLong 自行维护#四、生产建议 🟡
// 场景一:只需要估计大小
// ConcurrentHashMap.size() 是可以的
// 场景二:需要精确计数
AtomicInteger size = new AtomicInteger(0);
map.put(key, value);
size.incrementAndGet();
map.remove(key);
size.decrementAndGet();
// 场景三:需要高性能计数(高并发)
LongAdder count = new LongAdder();
count.increment();
count.sum();#五、追问升级
面试官:"LongAdder 和 AtomicLong 怎么选?"
// 低并发:AtomicLong(简单)
AtomicLong counter = new AtomicLong(0);
counter.incrementAndGet(); // 线程安全
// 高并发:LongAdder(性能更好)
LongAdder counter = new LongAdder();
counter.increment(); // 线程安全,无竞争
// LongAdder 原理:
// 多个线程更新不同的 Cell 分段
// sum() 时累加所有分段
// 比 AtomicLong 的 CAS 重试开销小