在多線程環境下,多個線程同時存取共享資源時,極易發生資料競爭導致結果錯誤。例如兩個線程各自對一個計數器執行 1,000,000 次遞增操作,理論結果應為 2,000,000,但實際執行往往小於該值。原因在於 i++ 並非原子操作,包含讀取、修改、寫回三個步驟,線程切換會導致操作覆蓋。
為從根本解決此問題,必須保證多線程對共享變數操作時的完全同步:即當線程 A 正在寫入時,線程 B 不能寫入,甚至不能讀取。Java 提供的 synchronized 關鍵字正是實現此同步機制的核心工具。
synchronized 的本質是對同步代碼塊加鎖,確保同一時間只有一個線程能進入同步塊,從而保障線程安全。它就像一把鎖,線程進入前必須獲得鎖,退出時自動釋放。
語法: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 對象,同步有效。
語法: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
}
}
語法: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 的鎖對象語義,是編寫正確並發程序的基石。