首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

ConcurrentHashMap 線程安全誤區與 synchronized 正確加鎖實踐

     編輯:關於C++

問題現象

使用 ConcurrentHashMap 進行多線程計數,8 個線程各自增 100 次,預期結果 800,實際運行多次結果均不等於 800。

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 8 線程並發執行:
if (!map.containsKey(KEY)) map.put(KEY, 1);
else map.put(KEY, map.get(KEY) + 1);  // 非原子操作!

根因分析

1. ConcurrentHashMap 的線程安全範圍

單個方法調用是原子的(get、put、containsKey 等),但組合操作不是原子的。上述代碼包含三步:

  1. map.get(KEY) - 讀取當前值
  2. + 1 - 計算新值
  3. map.put(KEY, newValue) - 寫回

線程 A 讀取時,線程 B 可能正在寫入,導致 A 讀到舊值,最終覆蓋 B 的更新(丟失更新)。

2. synchronized 加鎖失效原因

最初嘗試用 synchronized 修飾實例方法 addup(),但每個線程持有獨立的 MyTask 實例,鎖的是不同對象(this),無法互斥。

正確解決方案

方案一:鎖共享對象(推薦)

// 所有線程共享同一把鎖
private static final Object LOCK = new Object();

// 在 run() 中:
synchronized (LOCK) {
    addup();  // 或直接在這裡寫邏輯
}

方案二:使用 ConcurrentHashMap 原子操作 API (Java 8+)

// computeIfAbsent + merge 組合實現原子自增
map.computeIfAbsent(KEY, k -> 0);
map.merge(KEY, 1, Integer::sum);  // 原子:old + 1

// 或更簡潔:
map.compute(KEY, (k, v) -> v == null ? 1 : v + 1);

方案三:LongAdder / AtomicLong (最高性能)

LongAdder counter = new LongAdder();
// 8 線程並發:
counter.increment();
// 最後:
map.put(KEY, counter.intValue());

完整修正代碼

public class Test40 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; ++i) {
            System.out.println(test());
        }
    }

    private static int test() throws InterruptedException {
        // 方案三:LongAdder 最高效,最後轉 Map
        Map<String, Integer> map = new HashMap<>();
        LongAdder counter = new LongAdder();
        
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 8; ++i) {
            pool.execute(() -> {
                for (int j = 0; j < 100; ++j) counter.increment();
            });
        }
        pool.shutdown();
        pool.awaitTermination(1, TimeUnit.DAYS);
        
        map.put("key", counter.intValue());
        return map.get("key");
    }
}

核心原理總結

概念關鍵點
ConcurrentHashMap 線程安全單方法原子,復合操作非原子
synchronized 鎖對象必須是所有線程共享的對象
判斷同一對象使用 == 比較引用地址
最佳計數器LongAdder (高並發) / AtomicLong (低並發)

避坑指南

  • ❌ 不要假設「線程安全集合」下的「讀-改-寫」自動安全
  • ❌ 不要在線程局部對象上加鎖期望全局互斥
  • ✅ 復合操作使用顯式鎖(共享對象)或原子 API
  • ✅ 純計數場景優先用 LongAdder,最後彙總到 Map
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved