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

Java 執行緒同步實戰:synchronized、死鎖、公平鎖、鎖優化

     編輯:Java 程式設計入門

一、為什麼需要同步?

多執行緒並發存取共享可變狀態時,若無同步會發生競態條件:

// 銀行帳戶經典案例
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 條件)

  1. 互斥:資源獨佔。
  2. 持有並等待:持有鎖 A 等待鎖 B。
  3. 不可搶佔:鎖只能持有者釋放。
  4. 循環等待: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。

七、總結檢查清單

  1. 共享可變狀態 → 必須同步。
  2. 優先用 synchronized (JVM 優化完善、語法簡潔)。
  3. 需可中斷/超時/公平/多條件 → ReentrantLock。
  4. 高併發計數 → LongAdder;複雜原子更新 → AtomicReference + CAS 循環。
  5. 死鎖預防:固定鎖順序、tryLock 超時、避免巢狀鎖。
  6. 善用 @ThreadSafe/@GuardedBy 註解文件化同步策略。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved