多線程協作中,線程間通信是核心難點。本文系統講解三種主流通信機制:同步鎖通信、輪詢通信、wait/notify 等待通知機制,通過完整代碼案例對比優缺點,揭示底層原理,指導實戰選型。
| 機制 | 核心原理 | CPU 佔用 | 響應延遲 | 代碼複雜度 | 適用場景 |
|---|---|---|---|---|---|
| synchronized 同步 | 共享內存 + 鎖互斥 | 低(阻塞不佔用 CPU) | 即時 | 低 | 簡單互斥、臨界區保護 |
| while 輪詢 | 主動循環檢查條件 | 極高(空轉消耗 CPU) | 取決於睡眠間隔 | 低 | 極簡單場景、教學演示 |
| wait/notify | 對象監視器等待/通知 | 低(釋放 CPU 進入等待) | 即時 | 中(需正確協議) | 生產者消費者、複雜協作 |
多線程持有同一把鎖對象,通過鎖的互斥性實現「你執行完我再執行」的順序協作,本質是共享內存通信。
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,同步代碼塊可指定任意對象線程 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(); // 強制退出
}
}
}
}
結論:生產環境嚴禁使用裸輪詢,極端情況下可配合 Thread.sleep(短時間) 緩解,但仍不推薦。
基於對象監視器:
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();
}
}
// 等待方
synchronized(lock) {
while (條件不滿足) {
lock.wait();
}
// 執行任務
}
// 通知方
synchronized(lock) {
改變條件;
lock.notifyAll(); // 推薦 notifyAll 避免信號丟失
}
若生產者先運行,迅速添加 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 更靈活 | 需要多條件等待的複雜同步 |
BlockingQueue、CompletableFuture、Lock/Condition 等高級並發工具