一、為什麼需要同步?
多執行緒並發存取共享可變狀態時,若無同步會發生競態條件:
// 銀行帳戶經典案例
class Account {
int balance = 30000;
void withdraw(int amount) {
if (balance >= amount) { // 檢查
balance -= amount; // 扣款 (非原子)
}
}
}
// 兩執行緒同時 withdraw(20000) → 可能都通過檢查 → 余額 -10000
二、synchronized 關鍵字
2.1 三種鎖定目標
| 寫法 | 鎖物件 | 範圍 |
| 同步實例方法 | this | 整個方法 |
| 同步靜態方法 | ClassName.class | 整個方法 |
| 同步區塊 | 自定義 Object lock | 區塊內程式碼 |
2.2 修正銀行案例
class Account {
int balance = 30000;
public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
}
}
}
// 或細粒度鎖
private final Object lock = new Object();
public void withdraw(int amount) {
synchronized (lock) { ... }
}
三、死鎖
3.1 產生條件 (Coffman 條件)
- 互斥:資源獨佔。
- 持有並等待:持有鎖 A 等待鎖 B。
- 不可搶佔:鎖只能持有者釋放。
- 循環等待:T1 等 T2,T2 等 T1...
3.2 經典死鎖程式碼
Object lock1 = new Object(), lock2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock1) {
sleep(100);
synchronized (lock2) { System.out.println("t1 done"); }
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
sleep(100);
synchronized (lock1) { System.out.println("t2 done"); }
}
});
t1.start(); t2.start(); // 永久阻塞
3.3 避免死鎖策略
- 固定鎖順序:全域規定先鎖
lock1 再鎖 lock2。
- tryLock 超時:
ReentrantLock.tryLock(timeout) 失敗則釋放已持有鎖重試。
- 避免巢狀鎖:重構程式碼,單一鎖保護不變式。
- 使用並發集合:
ConcurrentHashMap、CopyOnWriteArrayList 內部無鎖/細粒度鎖。
四、ReentrantLock 進階功能
ReentrantLock lock = new ReentrantLock(true); // true = 公平鎖 (FIFO)
lock.lockInterruptibly(); // 可被 interrupt 中斷等待
boolean got = lock.tryLock(1, TimeUnit.SECONDS); // 超時獲取
Condition notEmpty = lock.newCondition();
lock.lock();
try {
while (queue.isEmpty()) notEmpty.await(); // 釋放鎖等待
// 消費
notEmpty.signalAll();
} finally { lock.unlock(); }
五、鎖優化 (JVM 內建)
| 優化技術 | 原理 | 適用場景 |
| 偏向鎖 | 無競爭時,鎖偏向第一個獲得的執行緒,後續進出無 CAS 開銷 | 單執行緒存取、ThreadLocal 等 |
| 輕量級鎖 | 低競爭時,用 CAS 修改 Mark Word 指向執行緒棧鎖記錄 | 交替存取、短關鍵區 |
| 膨脹為重量級鎖 | 高競爭時,改用 OS Mutex,執行緒阻塞掛起 | 高併發、長關鍵區 |
| 鎖消除 | JIT 編譯期逃逸分析發現鎖物件不逃逸,直接移除同步 | StringBuffer 區域變數、同步區塊無共享 |
| 鎖粗化 | 合併相鄰同步區塊,減少鎖開銷 | 循環內同步、連續同步呼叫 |
六、volatile 與原子類別
volatile:保證可見性 + 禁止重排,**不保證原子性**。
AtomicInteger 等:CAS 無鎖原子操作,適用計數器、狀態標記。
LongAdder (Java 8+):高併發計數器,分段 CAS,效能優於 AtomicLong。
七、總結檢查清單
- 共享可變狀態 → 必須同步。
- 優先用
synchronized (JVM 優化完善、語法簡潔)。
- 需可中斷/超時/公平/多條件 →
ReentrantLock。
- 高併發計數 →
LongAdder;複雜原子更新 → AtomicReference + CAS 循環。
- 死鎖預防:固定鎖順序、tryLock 超時、避免巢狀鎖。
- 善用
@ThreadSafe/@GuardedBy 註解文件化同步策略。