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

Java 線程通信三大核心機制:synchronized、輪詢、wait/notify 深度解析

     編輯:Java 綜合教學

前言

多線程協作中,線程間通信是核心難點。本文系統講解三種主流通信機制:同步鎖通信、輪詢通信、wait/notify 等待通知機制,通過完整代碼案例對比優缺點,揭示底層原理,指導實戰選型。

一、機制對比總覽

機制核心原理CPU 佔用響應延遲代碼複雜度適用場景
synchronized 同步共享內存 + 鎖互斥低(阻塞不佔用 CPU)即時低簡單互斥、臨界區保護
while 輪詢主動循環檢查條件極高(空轉消耗 CPU)取決於睡眠間隔低極簡單場景、教學演示
wait/notify對象監視器等待/通知低(釋放 CPU 進入等待)即時中(需正確協議)生產者消費者、複雜協作

二、機制一:synchronized 同步通信(共享內存模型)

核心思想

多線程持有同一把鎖對象,通過鎖的互斥性實現「你執行完我再執行」的順序協作,本質是共享內存通信。

代碼案例

public class MyObject {
    public synchronized void methodA() { /* 耗時操作 */ }
    public synchronized void methodB() { /* 另一操作 */ }
}

public class ThreadA extends Thread {
    private MyObject obj;
    public void run() { obj.methodA(); }
}

public class ThreadB extends Thread {
    private MyObject obj;
    public void run() { obj.methodB(); }
}

public class Main {
    public static void main(String[] args) {
        MyObject sharedObj = new MyObject();  // 共享同一把鎖
        new ThreadA(sharedObj).start();
        new ThreadB(sharedObj).start();
        // ThreadB 必須等待 ThreadA 釋放鎖後才能執行 methodB()
    }
}

關鍵點

  • 鎖對象必須相同(此處為 sharedObj)
  • 同步方法鎖為 this,同步代碼塊可指定任意對象
  • 實現的是互斥訪問而非主動通知,屬於被動協作

三、機制二:while 輪詢通信(忙等待/自旋)

核心思想

線程 A 改變條件,線程 B 通過 while(true) 不斷檢查條件,條件滿足則執行後續邏輯。

代碼案例

public class MyList {
    private List list = new ArrayList<>();
    public void add() { list.add("element"); }
    public int size() { return list.size(); }
}

// 生產者
public class ThreadA extends Thread {
    private MyList list;
    public void run() {
        for (int i = 0; i < 10; i++) {
            list.add();
            System.out.println("添加了 " + (i+1) + " 個元素");
            Thread.sleep(1000);
        }
    }
}

// 消費者(輪詢)
public class ThreadB extends Thread {
    private MyList list;
    public void run() {
        while (true) {  // 死循環檢查
            if (list.size() == 5) {
                System.out.println("達到 5 個,準備退出");
                throw new InterruptedException();  // 強制退出
            }
        }
    }
}

致命缺陷

  • ❌ 極度浪費 CPU:線程 B 佔用時間片只做條件判斷,無實質工作
  • ❌ 響應延遲不可控:取決於線程調度,可能剛檢查完條件就滿足,需等下一輪
  • ❌ 難以優雅退出:需拋出異常或設置標誌位中斷

結論:生產環境嚴禁使用裸輪詢,極端情況下可配合 Thread.sleep(短時間) 緩解,但仍不推薦。

四、機制三:wait/notify 等待通知機制(標準協作模式)

核心原理

基於對象監視器:

  • wait():當前線程釋放鎖,進入等待集合,阻塞直到被喚醒或超時
  • notify():隨機喚醒一個在該對象上 wait 的線程,進入阻塞隊列競爭鎖
  • notifyAll():喚醒所有等待線程

前置條件:調用者必須持有該對象的鎖(在 synchronized 塊/方法中調用),否則拋出 IllegalMonitorStateException。

代碼案例:生產者-消費者標準實現

public class SharedList {
    private static List list = new ArrayList<>();
    public static void add() { list.add("item"); }
    public static int size() { return list.size(); }
}

// 消費者:等待條件滿足
public class ConsumerThread extends Thread {
    private Object lock;
    public ConsumerThread(Object lock) { this.lock = lock; }
    
    public void run() {
        synchronized (lock) {  // 必須持有鎖
            try {
                while (SharedList.size() != 5) {  // 用 while 防虛假喚醒
                    System.out.println("wait begin " + System.currentTimeMillis());
                    lock.wait();  // 釋放鎖,進入等待
                    System.out.println("wait end " + System.currentTimeMillis());
                }
                // 條件滿足,繼續執行消費邏輯
                System.out.println("消費者開始工作...");
            } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

// 生產者:改變條件並通知
public class ProducerThread extends Thread {
    private Object lock;
    public ProducerThread(Object lock) { this.lock = lock; }
    
    public void run() {
        synchronized (lock) {  // 必須持有同一把鎖
            for (int i = 0; i < 10; i++) {
                SharedList.add();
                if (SharedList.size() == 5) {
                    lock.notify();  // 喚醒一個等待線程
                    System.out.println("已發出通知");
                }
                System.out.println("添加了 " + (i+1) + " 個元素");
                Thread.sleep(1000);
            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();  // 共享鎖對象
        new ConsumerThread(lock).start();
        Thread.sleep(50);  // 確保消費者先進入 wait
        new ProducerThread(lock).start();
    }
}

關鍵最佳實踐

  1. 必須在 synchronized 塊中調用 wait/notify
  2. 使用 while 循環而非 if 判斷條件:防止虛假喚醒(spurious wakeup)
  3. 標準協議:
    // 等待方
    synchronized(lock) {
        while (條件不滿足) {
            lock.wait();
        }
        // 執行任務
    }
    
    // 通知方
    synchronized(lock) {
        改變條件;
        lock.notifyAll();  // 推薦 notifyAll 避免信號丟失
    }
  4. 優先使用 notifyAll():多條件等待時,notify() 可能喚醒錯誤線程導致信號丟失

經典陷阱:通知過早導致信號丟失

若生產者先運行,迅速添加 5 個元素並調用 notify(),此時消費者尚未 wait(),通知將丟失。消費者後續 wait() 將永久阻塞。

解決方案:

  • 確保消費者先進入 wait()(如示例中 Thread.sleep(50) 保證順序,但不靠譜)
  • 引入狀態標誌位:生產者設置 ready = true,消費者檢查標誌位決定是否 wait()
  • 使用更高級的 Condition / CountDownLatch / BlockingQueue 替代底層 wait/notify

五、現代替代方案(生產首選)

工具類核心優勢典型場景
BlockingQueue線程安全隊列,內置阻塞取/放,無需手動同步生產者消費者、任務隊列
CountDownLatch倒計時閂,一組線程等待另一組線程完成多線程並行初始化、分階段計算
CyclicBarrier循環柵欄,一組線程相互等待至屏障點並行計算階段同步
Semaphore信號量,控制同時訪問資源的線程數資源池、限流
CompletableFuture異步編排,支持 thenApply/thenCompose/allOf 等組合複雜異步任務編排
Lock + Condition顯式鎖 + 多條件變量,比 wait/notify 更靈活需要多條件等待的複雜同步

六、總結

  1. synchronized 解決互斥訪問,屬於被動協作
  2. 輪詢 極其低效,生產環境禁用
  3. wait/notify 是 Java 線程協作的基石,但易用錯、難調試
  4. 實戰首選:BlockingQueue、CompletableFuture、Lock/Condition 等高級並發工具
  5. 必須掌握 wait/notify 原理,因為高級工具底層均基於此實現,排查死鎖/活鎖時需分析監視器狀態
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved