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

Effective Java 項目 11:同步訪問共享可變數據的正確姿勢

     編輯:Java 綜合教學

核心原則

當多個線程共享可變數據時,每個讀或寫該數據的線程都必須執行同步。缺乏同步會導致數據競爭、內存可見性問題,甚至 JVM 優化導致的邏輯錯誤。

反面教材:沒有同步的停止線程

public class StopThread {
    private static boolean stopRequested; // 非 volatile,無同步

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested) { // 可能永遠讀不到主線程的修改
                ++i;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true; // 主線程修改,但背景線程可能不可見
    }
}

現象:程序永不終止。原因:JIT 編譯器可能將循環優化為 if (!stopRequested) while(true) ...,且無同步時主線程的寫入不保證對背景線程可見。

方案一:雙向同步方法(鎖同步)

private static synchronized void requestStop() { stopRequested = true; }
private static synchronized boolean stopRequested() { return stopRequested; }

讀寫方法均加 synchronized,確保互斥訪問與內存可見性(happens-before 關係)。缺點:鎖開銷較大,易產生爭用。

方案二:volatile 關鍵字(輕量級同步)

private static volatile boolean stopRequested2;

volatile 保證:

  • 可見性:寫入對後續讀取立即可見。
  • 禁止指令重排:防止編譯器/CPU 將讀寫重排序。

適用場景:單個變量的讀寫、狀態標誌、雙重檢查鎖單例模式。

代碼對比驗證

@Test
public void testSyncMethod() { // 使用 synchronized 方法
    Thread t = new Thread(() -> {
        while (!stopRequested()) { /* busy */ }
    });
    t.start();
    TimeUnit.SECONDS.sleep(1);
    requestStop(); // 1秒後停止,線程正常退出
}

@Test
public void testVolatile() { // 使用 volatile
    Thread t = new Thread(() -> {
        while (!stopRequested2) { /* busy */ }
    });
    t.start();
    TimeUnit.SECONDS.sleep(1);
    stopRequested2 = true; // 正常退出
}

深度補充:為什麼 volatile 夠用?

對於 boolean、int、引用類型等單次讀寫原子的操作,volatile 提供的語義已足夠。但若涉及複合操作(如 i++、check-then-act),仍需 synchronized 或 AtomicInteger 等原子類。

總結

方案適用場景優點缺點
synchronized 方法/塊複合操作、多變量協同、需要互斥語義完整、可重入性能開銷大、死風險
volatile單變量狀態標誌、單次寫入/讀取無鎖、輕量、不阻塞不保證複合操作原子性
Atomic 類計數器、CAS 操作無鎖高性能、API 豐富僅支持單變量原子操作

記住:同步不僅是互斥,更是內存可見性的契約。沒有同步,就沒有 happens-before,讀線程看到的可能是陈舊值甚至部分構造的對象。

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