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

Java 多線程核心:深度剖析 volatile 與 synchronized 的正確用法

     編輯:Java 相關知識

前言:多線程環境下的變數可見性與原子性

在 Java 多線程開發中,volatile 與 synchronized 是兩大核心同步機制。雖然兩者都能解決線程安全問題,但其適用場景、語義保證與性能開銷截然不同。本文將通過實戰代碼演示,深入剖析兩者的區別與正確使用姿勢。

問題複現:非原子操作導致的數據競爭

首先看一個經典的計數器例子:啟動 10,000 條線程,每條線程對共享變數 count 執行 count++ 操作。

public class VolatileTest extends Thread {
    private static int count = 0;

    @Override
    public void run() {
        count++; // 非原子操作:讀取、修改、寫入三步
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10000];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new VolatileTest();
            threads[i].start();
        }
        Thread.sleep(1000); // 等待所有線程結束
        System.out.println("最終計數:" + count); // 輸出遠小於 10000
    }
}

現象:預期輸出 10000,實際輸出往往只有 8000 左右。原因在於 count++ 並非原子操作,包含「讀取、加一、寫回」三步,多線程並發時會相互覆蓋。

錯誤嘗試:volatile 無法解決複合操作的原子性

很多開發者誤以為加上 volatile 就能解決線程安全問題,我們來驗證一下:

private volatile static int count = 0; // 僅添加 volatile 關鍵字

結果:輸出依然不是 10000。

深度解析:volatile 僅保證可見性(一線程寫入,其他線程立即可見)和有序性(禁止指令重排),但不保證原子性。對於 count++ 這類「先讀後寫」的複合操作,volatile 無法防止線程在讀取到舊值後、寫入新值前被其他線程搶佔修改。

正確方案:synchronized 保證原子性與可見性

使用 synchronized 同步塊(或方法)可將複合操作封裝為原子單元:

public class SynchronizedTest extends Thread {
    private static int count = 0;
    private static final Object LOCK = new Object(); // 專用鎖對象

    @Override
    public void run() {
        synchronized (LOCK) { // 同步塊,僅鎖住核心代碼
            count++;
        }
    }
    // main 方法同上,輸出穩定為 10000
}

關鍵點:

  • 鎖對象建議使用 private static final Object,避免使用 String 或基本類型包裝類(因常量池可能導致非預期共享鎖)。
  • synchronized 同時提供互斥性(同一時刻僅一線程持有鎖)、可見性(解鎖前將工作內存刷新到主內存)、有序性。

核心對比:volatile vs synchronized

維度volatilesynchronized
原子性❌ 僅保證單次讀/寫原子✅ 保證代碼塊整體原子
可見性✅ 立即刷新主內存✅ 解鎖時刷新主內存
有序性✅ 禁止指令重排✅ 鎖語義天然有序
阻塞性❌ 非阻塞,輕量級✅ 可能導致線程阻塞/上下文切換
適用場景狀態標誌、單次賦值、雙重檢查鎖複合操作、臨界區、多變數協同

進階:什麼時候用 volatile?

volatile 適用於單一變數的狀態標識場景,例如:

  • 線程停止標誌:volatile boolean running = true;
  • 雙重檢查鎖單例模式中的實例引用
  • 獨立的長整型/雙精度變數讀寫(JVM 將 64 位讀寫拆為兩次 32 位操作,volatile 保證原子)

總結

volatile 是「輕量級的 synchronized」,適用於單一變數、無上下文依賴的簡單場景;synchronized 則是重型武器,解決複合操作、多變數協同的線程安全問題。實戰中:能用 volatile 解決的堅決不用 synchronized,但涉及「先檢查後執行」等複合邏輯時,必須上 synchronized 或 Lock/原子類。

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