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

Java 多線程同步機制完全指南:Lock、Condition、synchronized 核心原理與實戰

     編輯:Java 相關知識

同步核心問題

多線程共享可變狀態時,複合操作(檢查-執行、讀取-修改-寫入)的非原子性導致競態條件。同步機制確保原子性、可見性、有序性。

顯式鎖:ReentrantLock

基本結構


private final Lock lock = new ReentrantLock();

public void safeMethod() {
    lock.lock();
    try {
        // 臨界區
    } finally {
        lock.unlock(); // 必須在 finally 釋放
    }
}

核心特性

  • 可重入:同一線程可多次獲取鎖,需對應次數釋放
  • 公平/非公平:`new ReentrantLock(true)` 公平模式(FIFO),預設非公平(吞吐優先)
  • 可中斷:`lock.lockInterruptibly()` 等待時響應中斷
  • 超時獲取:`lock.tryLock(timeout, unit)` 避免無限等待
  • 條件變量:`lock.newCondition()` 支援多條件隊列

條件變量:Condition

銀行轉賬經典案例


public class Bank {
    private final double[] accounts;
    private final Lock bankLock = new ReentrantLock();
    private final Condition sufficientFunds = bankLock.newCondition();
    
    public Bank(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }
    
    public void transfer(int from, int to, double amount) 
            throws InterruptedException {
        bankLock.lock();
        try {
            while (accounts[from] < amount) {
                sufficientFunds.await(); // 釋放鎖並等待
            }
            accounts[from] -= amount;
            accounts[to] += amount;
            sufficientFunds.signalAll(); // 喚醒所有等待線程
        } finally {
            bankLock.unlock();
        }
    }
}

await/signal 機制

  • `await()`:釋放鎖,進入條件等待隊列,被喚醒後重新獲取鎖
  • `signal()`:喚醒單一等待線程(隨機)
  • `signalAll()`:喚醒所有等待線程(推薦,避免信號丟失)
  • 必須在持有鎖時調用,否則拋 `IllegalMonitorStateException`

內建鎖:synchronized

同步方法


public synchronized void transfer(int from, int to, double amount) 
        throws InterruptedException {
    while (accounts[from] < amount) wait();
    accounts[from] -= amount;
    accounts[to] += amount;
    notifyAll();
}

同步塊


public void transfer(int from, int to, double amount) {
    synchronized (this) { // 或鎖專用對象
        // 臨界區
    }
}

等價關係

synchronizedLock + Condition
同步方法/塊lock.lock() / unlock()
wait()condition.await()
notify()condition.signal()
notifyAll()condition.signalAll()
單一條件隊列多條件隊列

客戶端鎖定

利用非線程安全對象的內建鎖保護複合操作:


public void transfer(Vector accounts, int from, int to, double amount) {
    synchronized (accounts) { // 鎖住 Vector 實例
        accounts.set(from, accounts.get(from) - amount);
        accounts.set(to, accounts.get(to) + amount);
    }
}

警告:脆弱、洩露實現細節、難維護。優先使用 `java.util.concurrent` 線程安全集合(`CopyOnWriteArrayList`、`ConcurrentHashMap`、阻塞隊列)。

進階同步工具類

工具類用途核心方法
CountDownLatch一組線程等待計數歸零countDown(), await()
CyclicBarrier線程組相互等待屏障點await(), barrierAction
Semaphore控制並發訪問許可證數acquire(), release()
Exchanger兩線程交換數據exchange(V)
Phaser階段性同步,支援動態註冊arriveAndAwaitAdvance()

選型指南

  1. 簡單臨界區:`synchronized` — 簡潔、JVM 優化好、無需手動釋放
  2. 需可中斷/超時/公平/多條件:`ReentrantLock`
  3. 生產者-消費者/狀態依賴:`Condition` 或阻塞隊列 (`BlockingQueue`)
  4. 一次性協調:`CountDownLatch`
  5. 循環屏障:`CyclicBarrier`
  6. 資源池/限流:`Semaphore`
  7. 高併發計數/累加:`LongAdder`、`AtomicLong` (無鎖優於有鎖)

最佳實踐檢查清單

  • [ ] 鎖釋放在 `finally` 塊
  • [ ] 避免在鎖內調用外部方法/IO/長耗時操作
  • [ ] 一致的鎖獲取順序防止死鎖
  • [ ] 使用 `tryLock` 避免無限阻塞
  • [ ] 偏好不可變對象/線程封閉/無鎖資料結構
  • [ ] 測試併發場景:壓測、線程模擬、模型檢查

死鎖經典案例與預防


// 錯誤:不同順序獲取鎖
Thread 1: lock(A) → lock(B)
Thread 2: lock(B) → lock(A)

// 正確:固定順序
private static final Object LOCK1 = new Object(), LOCK2 = new Object();
void method1() { synchronized(LOCK1) { synchronized(LOCK2) { ... } } }
void method2() { synchronized(LOCK1) { synchronized(LOCK2) { ... } } }

現代替代方案

  • CompletableFuture:非阻塞組合式異步編程
  • Parallel Streams:數據並行處理
  • Virtual Threads (JDK 21+):輕量級線程,同步阻塞開銷極低
  • Structured Concurrency (JDK 21+):結構化併發,自動錯誤傳播與取消
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved