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

Java 多線程核心剖析:Runnable vs Thread、start/run 機制、wait/notify/sleep、三線程交替打印 ABC 實戰

     編輯:Java 綜合教學

Runnable 與 Thread 兩種創建方式

維度繼承 Thread實現 Runnable
定義class MyThread extends Thread { run() {...} }class MyTask implements Runnable { run() {...} }
啟動new MyThread().start()new Thread(new MyTask()).start()
共享數據較難 (需額外傳遞)天然支援 (同一 Runnable 實例傳給多 Thread)
單繼承限制佔用繼承名額無限制,可再繼承其他類
推薦度低高 (官方推薦)

start() vs run() 本質區別

  • start():請求 JVM 創建新線程,線程調度器稍後調用 run(),實現併發。
  • run():單純方法調用,在主線程中順序執行,無併發效果。

wait/notify vs sleep 核心差異

面試題:三線程交替打印 ABC (各 10 次)

題目要求

建立三個線程 A/B/C,每個打印 10 次對應字母,要求併發運行、嚴格交替輸出 ABCABC... (共 30 次)。

核心思路:鏈式鎖傳遞

  • 每個線程持有兩把鎖:自己的鎖 self 與前驅線程的鎖 prev。
  • 線程啟動後嘗試同時獲取 prev 和 self (按固定順序避免死鎖)。
  • 獲得雙鎖後打印一次,prev.notify() 喚醒前驅線程,釋放 prev,在 prev.wait() 等待被後繼線程喚醒。
  • 形成環狀:A 等 C、B 等 A、C 等 B。

完整代碼

public class PrintABC implements Runnable {
    private final String name;
    private final Object prev;  // 前驅鎖
    private final Object self;  // 自身鎖

    public PrintABC(String name, Object prev, Object self) {
        this.name = name;
        this.prev = prev;
        this.self = self;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);
                    // self.notify(); // 此處不需要 notify self
                }
                try {
                    prev.wait(); // 等待前驅喚醒 (第一輪除外)
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object a = new Object(), b = new Object(), c = new Object();

        PrintABC taskA = new PrintABC("A", c, a); // A 等 C
        PrintABC taskB = new PrintABC("B", a, b); // B 等 A
        PrintABC taskC = new PrintABC("C", b, c); // C 等 B

        // 必須按順序啟動,確保初始鎖狀態正確
        new Thread(taskA).start();
        Thread.sleep(10); // 微小延遲確保啟動順序
        new Thread(taskB).start();
        Thread.sleep(10);
        new Thread(taskC).start();
    }
}

關鍵點解析

  1. 雙鎖順序:先鎖 prev 再鎖 self,所有線程一致,避免死鎖。
  2. 首輪啟動:主線程依次 start() 並 sleep(10),確保 A 先持有 c&a、B 持有 a&b、C 持有 b&c,打印第一輪 ABC 後各自 wait() 釋放 prev。
  3. 喚醒傳遞:A 打印完 wait() 在 c 上;C 打印完在 synchronized(c) 隱式釋放鎖時喚醒 A (JVM 保證)。同理 B←A、C←B。
  4. 輸出結果:ABCABCABCABCABCABCABCABCABCABC (30 字符)。

進階優化

  • 用 LockSupport.park/unpark 替代 wait/notify,無需 synchronized,語義更清晰。
  • 用 Phaser / CyclicBarrier 實現多方同步。
  • 用 CompletableFuture 串聯任務。

總結

掌握 Runnable 優於 Thread、start() 啟動併發、wait 釋放鎖 vs sleep 持有鎖、鏈式雙鎖實現線程有序交替,是 Java 多線程核心基本功。實戰中優先使用 java.util.concurrent 高級組件 (Lock、Condition、Phaser、CompletableFuture) 降低復雜度、提高可靠性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性Object.wait()Thread.sleep()
所屬類ObjectThread
鎖行為釋放鎖,進入等待隊列持有鎖,僅讓出 CPU
喚醒方式notify/notifyAll (同一鎖對象)時間到期自動喚醒
前提條件必須在 synchronized(lock) 塊中無
典型用途線程間協作 (生產消費者)定時暫停、重試延遲