互斥鎖是多線程環境下保護共享資源的基礎同步機制。本文通過計數器競態問題,示範 synchronized 關鍵字的正確使用姿勢與常見誤區。
public class LockDemo {
private int counter = 0;
public void increase() {
for (int k = 0; k < 10; k++) {
counter++; // 非原子操作:讀取-修改-寫入
}
System.out.println(Thread.currentThread().getName() + " 計數器值:" + counter);
}
public static void main(String[] args) {
LockDemo demo = new LockDemo();
Thread[] threads = new Thread[1000];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(demo::increase);
threads[i].start();
}
}
}
預期:最終計數器 = 1000 × 10 = 10,000
實際:遠小於 10,000,每次運行結果不同
this 或私有實例欄位ClassName.class 或私有靜態欄位public class LockDemo {
private static final Object LOCK = new Object(); // 靜態確保單一鎖物件
private int counter = 0;
public void increase() {
synchronized (LOCK) { // 關鍵區段
for (int k = 0; k < 10; k++) {
counter++;
}
System.out.println(Thread.currentThread().getName() + " 計數器值:" + counter);
}
}
// main 方法同前
}
輸出:每個線程輸出的計數器值嚴格遞增,最終穩定為 10,000。
| 鎖範圍 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
方法級 synchronized | 簡單、不易出錯 | 並發度低 | 低競爭、簡單邏輯 |
| 程式碼塊級 | 精確控制臨界區大小 | 需仔細設計鎖物件 | 高競爭、複雜邏輯 |
| 分段鎖 | 高並發度 | 實作複雜 | ConcurrentHashMap 風格 |
// 錯誤:每次呼叫 new Object(),線程鎖定不同物件,無互斥效果
synchronized (new Object()) { counter++; }
// 錯誤:字串常量池可能導致非預期共享
synchronized ("lock") { ... }
// 實例鎖
private final Object lock = new Object();
// 靜態鎖
private static final Object STATIC_LOCK = new Object();
// 使用 java.util.concurrent.locks.ReentrantLock 獲得更多控制
private final Lock lock = new ReentrantLock();
public void increase() {
lock.lock();
try {
// 臨界區
} finally {
lock.unlock(); // 必須在 finally 中釋放
}
}
synchronized 編譯為 monitorenter/monitorexit 位元組碼指令ReentrantLock(true)掌握互斥鎖核心在於:識別共享可變狀態、選擇正確鎖物件、最小化臨界區。這是構建正確並發程式的基石技能。