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

Java synchronized 關鍵字深度解析:用法、鎖機制與常見陷阱

     編輯:Java 相關知識

前言:為什麼需要 synchronized?

在多線程環境下,多個線程同時存取共享資源時,極易發生資料競爭導致結果錯誤。例如兩個線程各自對一個計數器執行 1,000,000 次遞增操作,理論結果應為 2,000,000,但實際執行往往小於該值。原因在於 i++ 並非原子操作,包含讀取、修改、寫回三個步驟,線程切換會導致操作覆蓋。

為從根本解決此問題,必須保證多線程對共享變數操作時的完全同步:即當線程 A 正在寫入時,線程 B 不能寫入,甚至不能讀取。Java 提供的 synchronized 關鍵字正是實現此同步機制的核心工具。

synchronized 的核心作用

synchronized 的本質是對同步代碼塊加鎖,確保同一時間只有一個線程能進入同步塊,從而保障線程安全。它就像一把鎖,線程進入前必須獲得鎖,退出時自動釋放。

三種主要用法詳解

1. 指定對象加鎖(同步代碼塊)

語法:synchronized (lockObject) { ... }

鎖對象必須是 static 最終不可變的,否則每次 new Thread 時創建不同實例,鎖失效。示例:

public class AccountingSync implements Runnable {
    final static Object LOCK = new Object();
    static int i = 0;

    public void increase() { i++; }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            synchronized (LOCK) { increase(); }
        }
    }
    // main 方法省略
}

此處兩個線程共用同一 LOCK 對象,同步有效。

2. 直接作用於實例方法

語法:public synchronized void method() { ... }

等同於 synchronized (this) { ... },鎖住當前實例。關鍵點:創建線程時必須使用同一個 Runnable 實例,否則鎖不在同一對象上。示例:

public class AccountingSync implements Runnable {
    static int i = 0;

    public synchronized void increase() { i++; }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) { increase(); }
    }

    public static void main(String[] args) throws InterruptedException {
        AccountingSync sync = new AccountingSync(); // 單一實例
        Thread t1 = new Thread(sync);
        Thread t2 = new Thread(sync);
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println(i); // 輸出 2000000
    }
}

3. 直接作用於靜態方法

語法:public static synchronized void method() { ... }

等同於 synchronized (ClassName.class) { ... },鎖住當前類的 Class 對象。不要求線程共用同一實例,天然適合多實例場景。示例:

public class AccountingSync implements Runnable {
    static int i = 0;

    public static synchronized void increase() { i++; }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) { increase(); }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AccountingSync());
        Thread t2 = new Thread(new AccountingSync()); // 不同實例也能同步
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println(i); // 輸出 2000000
    }
}

經典錯誤:對不可變對象加鎖

許多開發者嘗試對 Integer 等不可變對象加鎖:

static Integer i = 0;
synchronized (i) { i++; } // 錯誤!

原因: Integer 不可變,i++ 實際上是 i = Integer.valueOf(i + 1),每次生成新對象。線程鎖住的對象不斷變化,同步失效。

解決: 改用上述三種正確方式之一,或使用 AtomicInteger 等原子類。

總結

  • 同步代碼塊:靈活,可精確控制鎖範圍與粒度。
  • 實例方法:簡潔,但要求共用實例。
  • 靜態方法:類級別鎖,無需共用實例。

掌握 synchronized 的鎖對象語義,是編寫正確並發程序的基石。

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