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

Java 多執行緒資源共享陷阱:從錯誤範例看同步必要性

     編輯:Java 相關知識

問題場景

每個執行緒擁有兩個計數器 count1、count2,在 run() 中依序遞增並顯示。邏輯上它們應始終相等,但引入 Watcher 監視執行緒後,卻發現兩者頻繁出現不等狀態(標籤顯示 "Unsynched")。

錯誤代碼核心片段

public void run() {
    while (true) {
        t1.setText(Integer.toString(count1++));
        t2.setText(Integer.toString(count2++));
        try { sleep(500); } catch (InterruptedException e) {}
    }
}

public void synchTest() {
    Sharing1.incrementAccess(); // 訪問計數
    if (count1 != count2) l.setText("Unsynched");
}

根因分析:執行緒調度的非確定性

雖然 count1++ 與 count2++ 相鄰,但 JVM 可能在兩行之間**暫停當前執行緒**,切換至 Watcher 執行緒執行 synchTest()。此時 count1 已更新而 count2 尚未更新,導致檢測到不一致。

這揭示了多執行緒環境下的核心問題:**無法預測執行緒何時運行、何時被掛起**。共享資源在關鍵區段必須受保護,否則會出現競態條件。

同步解決方案

1. synchronized 關鍵字

public synchronized void run() { ... } // 整個方法同步,粒度較粗

// 或細粒度同步塊
public void run() {
    while (true) {
        synchronized(this) {
            t1.setText(Integer.toString(count1++));
            t2.setText(Integer.toString(count2++));
        }
        try { sleep(500); } catch (InterruptedException e) {}
    }
}

2. 使用 java.util.concurrent 套件(現代最佳實踐)

private final AtomicInteger count1 = new AtomicInteger(0);
private final AtomicInteger count2 = new AtomicInteger(0);

public void run() {
    while (true) {
        int c1 = count1.incrementAndGet();
        int c2 = count2.incrementAndGet();
        t1.setText(String.valueOf(c1));
        t2.setText(String.valueOf(c2));
        // AtomicInteger 保證單次操作原子性,但兩個獨立原子操作仍非事務性
        // 若需強一致性,仍需額外同步或使用 LongAdder + 單一計數器設計
    }
}

設計原則總結

  • 識別共享可變狀態:凡多執行緒可見且可寫的變數,皆為共享資源。
  • 縮小關鍵區:同步塊應僅包含必要操作,減少爭用。
  • 優先使用高階併發工具:Atomic*、ConcurrentHashMap、Lock、Semaphore 等比低層 synchronized 更安全、效能更好。
  • 不可變物件:無狀態或不可變設計可徹底避免同步問題。

結語

本範例雖簡單,卻道出了多執行緒程式設計的核心挑戰:**共享資源的協同存取**。理解「檢查-執行」非原子性、掌握同步機制與現代併發工具,是寫出正確、高效併發程式的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved