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

Java 多線程基礎總結(七):深入理解 ReentrantLock 與 Condition 機制

     編輯:Java 相關知識

在前幾篇文章中,我們探討了無鎖同步機制,這固然是解決多線程問題的理想方案。然而在實際開發中,許多場景仍不得不依賴鎖同步來保證線程安全。自 JDK 5 起,Java 提供了兩種並行訪問控制機制:synchronized 關鍵字與 ReentrantLock。既然 synchronized 已經介紹過,本文將重點分析 ReentrantLock 如何改善傳統鎖的不足。

synchronized 的兩大痛點

實際使用中,synchronized 主要存在以下限制:

  1. 單一條件限制:每個鎖只能關聯一個「條件」(即 wait()/notify()),大量併發線程的等待與喚醒管理極其困難。
  2. 無法中斷等待:線程競爭鎖失敗時只能不斷嘗試獲取,無法響應中斷信號,導致高併發下性能下降。

ReentrantLock 的改進示例

以下代碼演示了 ReentrantLock 如何通過 lockInterruptibly() 響應線程中斷:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockSample {
    public static void main(String[] args) {
        testSynchronized();
        testReentrantLock();
    }

    public static void testReentrantLock() {
        final SampleSupport1 support = new SampleSupport1();
        Thread first = new Thread(() -> {
            try { support.doSomething(); }
            catch (InterruptedException e) { e.printStackTrace(); }
        });
        Thread second = new Thread(() -> {
            try { support.doSomething(); }
            catch (InterruptedException e) {
                System.out.println("Second Thread Interrupted without executing counter++");
            }
        });
        executeTest(first, second);
    }

    public static void testSynchronized() {
        final SampleSupport2 support2 = new SampleSupport2();
        Runnable runnable = () -> support2.doSomething();
        Thread third = new Thread(runnable);
        Thread fourth = new Thread(runnable);
        executeTest(third, fourth);
    }

    public static void executeTest(Thread a, Thread b) {
        a.start();
        try {
            Thread.sleep(100);
            b.start();
            Thread.sleep(1000);
            b.interrupt(); // 1秒後嘗試中斷第二個線程
        } catch (InterruptedException e) { e.printStackTrace(); }
    }
}

abstract class SampleSupport {
    protected int counter;
    public void startTheCountdown() {
        long currentTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - currentTime <= 5000) { /* busy wait */ }
    }
}

class SampleSupport1 extends SampleSupport {
    private final ReentrantLock lock = new ReentrantLock();
    public void doSomething() throws InterruptedException {
        lock.lockInterruptibly(); // 關鍵點:可中斷獲取鎖
        System.out.println(Thread.currentThread().getName() + " will execute counter++.");
        startTheCountdown();
        try { counter++; }
        finally { lock.unlock(); }
    }
}

class SampleSupport2 extends SampleSupport {
    public synchronized void doSomething() {
        System.out.println(Thread.currentThread().getName() + " will execute counter++.");
        startTheCountdown();
        counter++;
    }
}

運行結果分析:

  • synchronized 版本:第二個線程即使被 interrupt() 仍會繼續等待約 5 秒獲取鎖並執行 counter++。
  • ReentrantLock 版本:第二個線程在等待鎖期間收到中斷信號,拋出 InterruptedException 並退出競爭,不再執行 counter++。

多條件變量:Condition 機制

ReentrantLock 另一大優勢是支持多個 Condition 對象,實現精細化的線程協調。以 ArrayBlockingQueue 為例:

public class ArrayBlockingQueue<E> extends AbstractQueue<E> {
    private final ReentrantLock lock;
    private final Condition notEmpty; // 等待取出的線程
    private final Condition notFull;  // 等待放入的線程

    public ArrayBlockingQueue(int capacity, boolean fair) {
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

    public void put(E e) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (count == items.length) notFull.await(); // 隊列滿,等待 notFull
            insert(e);
            notEmpty.signal(); // 喚醒等待取出的線程
        } finally { lock.unlock(); }
    }

    public E take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (count == 0) notEmpty.await(); // 隊列空,等待 notEmpty
            return extract();
        } finally { lock.unlock(); }
    }
    // ... insert/extract 省略
}

這裡 notEmpty 和 notFull 分別管理「消費者」與「生產者」線程,避免了 synchronized 單一條件導致的「驚群效應」與管理混亂。

總結與建議

  • 優點:可中斷獲取鎖、多條件變量、公平鎖選項、性能與 synchronized 相當(JDK 6+ 優化後)。
  • 缺點:必須在 finally 塊中顯式調用 unlock(),否則易發生死鎖;代碼較繁瑣。
  • 最佳實踐:若無需上述高級功能,優先使用 synchronized(語法簡潔、JVM 自動釋放鎖、逃逸分析優化更好)。

下一篇將介紹 ReentrantReadWriteLock,實現讀寫分離的部分鎖機制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved