多執行緒環境下的共享計數器是經典併發問題。本文從單例模式確保唯一實例出發,對比 synchronized、ReentrantLock、AtomicLong、LongAdder 四種實作方式,分析原理、效能、適用場景,提供生產環境選型建議。
public interface Counter {
long incrementAndGet();
long get();
void reset();
}
public final class SynchronizedCounter implements Counter {
private static final SynchronizedCounter INSTANCE = new SynchronizedCounter();
private long count = 0;
private SynchronizedCounter() {}
public static SynchronizedCounter getInstance() { return INSTANCE; }
@Override public synchronized long incrementAndGet() { return ++count; }
@Override public synchronized long get() { return count; }
@Override public synchronized void reset() { count = 0; }
}
public final class LockCounter implements Counter {
private static final LockCounter INSTANCE = new LockCounter();
private long count = 0;
private final Lock lock = new ReentrantLock(); // 公平鎖: new ReentrantLock(true)
private LockCounter() {}
public static LockCounter getInstance() { return INSTANCE; }
@Override public long incrementAndGet() {
lock.lock();
try { return ++count; } finally { lock.unlock(); }
}
@Override public long get() {
lock.lock();
try { return count; } finally { lock.unlock(); }
}
@Override public void reset() {
lock.lock();
try { count = 0; } finally { lock.unlock(); }
}
}
public final class AtomicCounter implements Counter {
private static final AtomicCounter INSTANCE = new AtomicCounter();
private final AtomicLong count = new AtomicLong(0);
private AtomicCounter() {}
public static AtomicCounter getInstance() { return INSTANCE; }
@Override public long incrementAndGet() { return count.incrementAndGet(); }
@Override public long get() { return count.get(); }
@Override public void reset() { count.set(0); }
}
public final class LongAdderCounter implements Counter {
private static final LongAdderCounter INSTANCE = new LongAdderCounter();
private final LongAdder count = new LongAdder();
private LongAdderCounter() {}
public static LongAdderCounter getInstance() { return INSTANCE; }
@Override public long incrementAndGet() {
count.increment();
return count.sum(); // 注意:sum() 為快照值,非原子遞增瞬間值
}
@Override public long get() { return count.sum(); }
@Override public void reset() { count.reset(); }
}
| 維度 | synchronized | ReentrantLock | AtomicLong (CAS) | LongAdder |
|---|---|---|---|---|
| 核心機制 | 監視器鎖 (OS 互斥量) | AQS 隊列同步器 | Compare-And-Swap (CPU 指令) | 分段計數器 + 基值 |
| 爭用時行為 | 執行緒阻塞/喚醒 (上下文切換) | 阻塞/喚醒,可中斷、可超時 | 自旋重試 (CPU 忙等待) | 分散到不同 Cell,極低爭用 |
| 低併發效能 | 良好 (JDK 6+ 偏向鎖/輕量鎖優化) | 略低於 synchronized | 極佳 | 良好 (略高記憶體) |
| 高併發效能 | 急劇下降 (鎖競爭) | 同 synchronized | 下降 (CAS 失敗重試) | 線性擴展 (最佳) |
| 記憶體開銷 | 極低 | 低 | 低 | 高 (多 Cell 陣列) |
| 功能豐富度 | 基礎 | 高 (條件變數、公平鎖、可中斷) | 中 (CAS 系列操作) | 低 (累加/求和/重置) |
| JDK 版本 | 所有 | 1.5+ | 1.5+ | 1.8+ |
Thread Count | synchronized | AtomicLong | LongAdder -------------|--------------|------------|---------- 1 | 12 ms | 8 ms | 10 ms 4 | 45 ms | 22 ms | 11 ms 16 | 210 ms | 95 ms | 13 ms 64 | 1200 ms | 480 ms | 18 ms
結論:LongAdder 在高併發下壓倒性優勢;低併發 AtomicLong 略勝;synchronized 易讀、除錯友善,中低併發足夠。
JDK < 1.8?
├── 是 → 併發低/中 → synchronized
│ 併發極高 → ReentrantLock (可中斷/公平) 或 分段鎖手寫
└── 否 (JDK 1.8+) → 需精確即時值 (incrementAndGet 回傳最新值)?
├─ 是 → 併發中/低 → AtomicLong
│ 併發極高 → LongAdder + 接受 sum() 為快照值
└─ 否 (僅需最終統計/日誌/監控) → LongAdder (首選)
原文提到「計數與輸出一併加鎖」。若需「遞增並取得最新值輸出」為原子操作:
// synchronized 版
public synchronized void incrementAndLog(String threadName) {
long current = ++count;
System.out.println(threadName + " count=" + current);
}
// AtomicLong 版 (需自行組合)
public void incrementAndLog(String threadName) {
long current = count.incrementAndGet();
System.out.println(threadName + " count=" + current); // 非原子:計數與印出中間可被插入
}
// 真正原子的「計數+輸出」需鎖或同步區塊
public void incrementAndLogAtomic(String threadName) {
synchronized (this) { // 或 lock.lock()
long current = count.incrementAndGet();
System.out.println(threadName + " count=" + current);
}
}
LongAdder.incrementAndGet() 不存在,只有 increment() (void) + sum() (長整數快照)AtomicLong.getAndIncrement() 回傳舊值,incrementAndGet() 回傳新值reset()) 在高併發下非原子 (LongAdder.reset() 會重新建立 Cell 陣列),需在維護窗口執行此分析提供從傳統鎖到現代無鎖計數器的完整技術棧視角,可根據 JDK 版本、併發量、功能需求精準選型。