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

Java 線程同步與線程間通信深度解析:synchronized、Lock、wait/notify、Condition、生產者消費者模式

     編輯:Java 相關知識

多線程編程的核心挑戰是共享數據同步與線程協作通信。本文系統講解 synchronized、ReentrantLock、wait/notify、Condition 四大同步原語的語法、內存語義、底層實現、性能特徵,並以生產者-消費者為主線串聯實戰模式,附 JDK 源碼級剖析與避坑指南。

一、同步原語全景對比

維度synchronizedReentrantLock
語法關鍵字,隱式獲取/釋放API,顯式 lock()/unlock()
可中斷不可中斷(阻塞直到獲取)lockInterruptibly() 可響應中斷
超時獲取不支持tryLock(timeout) 支持
公平性非公平可選公平/非公平(構造器參數)
條件變量單一 wait/notify多 Condition(精確喚醒)
性能 (JDK 6+)偏向鎖/輕量級鎖/重量級鎖自適應優化穩定,無鎖升級開銷
監控診斷JVM 內建,jstack 可見需自行埋點或 JMX

選型建議:簡單同步用 synchronized(JVM 優化最好、代碼最簡);需中斷/超時/多條件/公平鎖 → ReentrantLock。

二、synchronized 深度剖析

1. 三種鎖定目標

// 1. 實例方法 → 鎖當前對象 this
public synchronized void instanceMethod() { }

// 2. 靜態方法 → 鎖當前類 Class 對象
public static synchronized void staticMethod() { }

// 3. 同步塊 → 鎖指定對象(推薦:縮小鎖範圍)
public void blockSync() {
    synchronized (this) { /* 臨界區 */ }
    // 或專用鎖對象
    private final Object lock = new Object();
    synchronized (lock) { /* 臨界區 */ }
}

2. 鎖升級機制 (JDK 6+)

無鎖 → 偏向鎖 (Biased) → 輕量級鎖 → 重量級鎖
     (單線程)      (CAS)        (自旋)       (OS Mutex)

• 偏向鎖:首次獲取線程記錄在對象頭,後續同線程無 CAS 開銷。
• 輕量級鎖:多線程交替,CAS 修改對象頭指向棧鎖記錄。
• 重量級鎖:競爭激烈/自旋超限,膨脹為 ObjectMonitor,線程進入 WAITING 狀態。

3. 內存語義

  • 獲取鎖:清除工作內存,從主內存讀取共享變量最新值(volatile 語義)。
  • 釋放鎖:將工作內存共享變量刷新回主內存(volatile 語義)。
  • 保證可見性與互斥性,不保證原子性(複合操作需額外同步)。

三、ReentrantLock 核心特性

ReentrantLock lock = new ReentrantLock(); // 非公平
// ReentrantLock lock = new ReentrantLock(true); // 公平

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

// 進階能力
lock.lockInterruptibly();           // 可中斷獲取
lock.tryLock(1, TimeUnit.SECONDS);  // 超時獲取
Condition notFull = lock.newCondition();  // 多條件變量
Condition notEmpty = lock.newCondition();

四、線程通信:wait/notify vs Condition

1. Object.wait/notify (synchronized 配套)

synchronized (lock) {
    while (conditionNotMet) { // while 防虛假喚醒
        lock.wait();          // 釋放鎖,進入 WAITING
    }
    // 條件滿足,執行任務
    lock.notify();      // 喚醒一個等待線程
    // lock.notifyAll(); // 喚醒所有(推薦:避免信號丟失)
}

2. Condition (Lock 配套,更強大)

ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull  = lock.newCondition();

lock.lock();
try {
    while (queue.isEmpty()) notEmpty.await(); // 釋放鎖,等待
    take();
    notFull.signal(); // 精確喚醒「非滿」等待者
} finally { lock.unlock(); }

核心優勢

  • 多條件變量:精確喚醒,避免 notifyAll 驚群效應。
  • 可中斷等待:awaitInterruptibly()。
  • 超時等待:await(timeout) 返回 boolean。
  • 公平喚醒:FIFO 隊列(公平鎖下)。

五、經典模式:生產者-消費者(有界緩衝區)

版本一:synchronized + wait/notify

class BoundedBuffer {
    private final V[] buf;
    private int putIdx = 0, takeIdx = 0, count = 0;

    @SuppressWarnings("unchecked")
    public BoundedBuffer(int capacity) { buf = (V[]) new Object[capacity]; }

    public synchronized void put(V v) throws InterruptedException {
        while (count == buf.length) wait(); // 滿等待
        buf[putIdx] = v;
        if (++putIdx == buf.length) putIdx = 0;
        count++;
        notifyAll(); // 唤醒消費者
    }

    public synchronized V take() throws InterruptedException {
        while (count == 0) wait(); // 空等待
        V v = buf[takeIdx];
        buf[takeIdx] = null; // GC 友好
        if (++takeIdx == buf.length) takeIdx = 0;
        count--;
        notifyAll(); // 唤醒生產者
        return v;
    }
}

版本二:ReentrantLock + 兩個 Condition(高性能、精確喚醒)

class BoundedBufferV2 {
    private final V[] buf;
    private int putIdx = 0, takeIdx = 0, count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    @SuppressWarnings("unchecked")
    public BoundedBufferV2(int capacity) { buf = (V[]) new Object[capacity]; }

    public void put(V v) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (count == buf.length) notFull.await();
            buf[putIdx] = v;
            if (++putIdx == buf.length) putIdx = 0;
            count++;
            notEmpty.signal(); // 只喚醒消費者
        } finally { lock.unlock(); }
    }

    public V take() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (count == 0) notEmpty.await();
            V v = buf[takeIdx];
            buf[takeIdx] = null;
            if (++takeIdx == buf.length) takeIdx = 0;
            count--;
            notFull.signal(); // 只喚醒生產者
            return v;
        } finally { lock.unlock(); }
    }
}

六、進階同步工具類(JUC)速查

七、避坑清單

  1. 鎖對象不可變:synchronized (new Object()) 每次新對象 → 無同步效果。用 final Object lock = new Object();。
  2. 字符串常量作鎖:synchronized ("lock") — 常量池共享,可能被無關代碼鎖住。用 new String("lock") 或專用對象。
  3. 釋放鎖前拋異常:lock.unlock() 必須在 finally,否則死鎖。
  4. 虛假喚醒:if 判斷 → while 循環。
  5. notify vs notifyAll:單條件可用 notify;多條件/不確定必用 notifyAll 或 Condition.signal。
  6. 死鎖預防:固定獲取鎖順序、tryLock(timeout) 超時退避、避免嵌套鎖。

同步與通信是多線程的雙子星。內化 synchronized 鎖升級、ReentrantLock 多條件、wait/notify 與 Condition 的語義差異,並熟練寫出版本二生產者-消費者,是並發編程從「能跑」到「穩健」的分水嶺。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
工具場景核心方法
CountDownLatch一組線程等待 N 個事件完成countDown()/await()
CyclicBarrierN 個線程相互等待彙聚await()/reset()
Semaphore限制並發訪問數(許可證)acquire()/release()
Phaser多階段同步,動態註冊arriveAndAwaitAdvance()
Exchanger兩線程交換數據exchange(V)
StampedLock讀寫鎖優化,樂觀讀tryOptimisticRead()/validate()