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

Java 多線程創建的兩種核心方式:Thread 繼承 vs Runnable 實現深度解析

     編輯:Java 綜合教學

前言

「創建線程」是 Java 併發編程的起點。JDK 提供了 繼承 Thread 類 和 實現 Runnable 接口 兩種基礎方式,底層均依賴 Thread 類與操作系統原生線程映射。本文深度解析兩種方式的源碼實現、內存模型差異、資源共享特性、線程生命週期控制,並給出生產環境選型建議。


一、Thread 類源碼核心結構


public class Thread implements Runnable {
    // 線程名稱
    private char[] name;
    // 目標任務(Runnable 實例)
    private Runnable target;
    // 線程組
    private ThreadGroup group;
    // 上下文類加載器
    private ClassLoader contextClassLoader;
    // 線程局部變量映射
    ThreadLocal.ThreadLocalMap threadLocals;
    
    // === 構造方法 ===
    // 1. 無參:自動生成名稱 "Thread-" + 次數
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    // 2. 指定名稱
    public Thread(String name) {
        init(null, null, name, 0);
    }
    
    // 3. 指定任務
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    // 4. 指定任務 + 名稱
    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    
    // 核心初始化邏輯
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        if (name == null) throw new NullPointerException("name cannot be null");
        this.name = name.toCharArray();
        this.target = target;
        // ... 其他初始化
    }
    
    // === 核心 run() 方法 ===
    @Override
    public void run() {
        if (target != null) {
            target.run(); // 委託給外部 Runnable 任務
        }
        // 否則執行子類重寫的 run()(繼承 Thread 方式)
    }
    
    // === 啟動線程:native 方法 ===
    public synchronized void start() {
        if (threadStatus != 0) throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0(); // JNI 調用操作系統創建線程
            started = true;
        } finally {
            if (!started) group.threadStartFailed(this);
        }
    }
    private native void start0();
}

二、方式一:繼承 Thread 類

2.1 代碼範例


class MyThread extends Thread {
    private int counter = 5;
    
    @Override
    public void run() {
        while (counter-- > 0) {
            System.out.println(Thread.currentThread().getName() + ": " + counter);
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        }
    }
}

public class ThreadExtendDemo {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        t1.setName("Thread-A");
        t2.setName("Thread-B");
        t1.start(); // 啟動線程,調用 run()
        t2.start();
    }
}

2.2 特點分析

維度說明
代碼結構線程對象 = 任務對象,職責合一
繼承限制無法繼承其他類(Java 單繼承)
資源共享每個 MyThread 實例擁有獨立字段(如 counter),默認不共享
訪問當前線程直接用 this 或 getName(),無需 Thread.currentThread()
適用場景簡單任務、教學演示、無需共享狀態的獨立線程

三、方式二:實現 Runnable 接口

3.1 代碼範例


class MyTask implements Runnable {
    private int counter = 5; // 共享狀態
    
    @Override
    public void run() {
        while (counter-- > 0) {
            System.out.println(Thread.currentThread().getName() + ": " + counter);
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        }
    }
}

public class RunnableImplDemo {
    public static void main(String[] args) {
        MyTask task = new MyTask(); // 單一任務實例
        Thread t1 = new Thread(task, "Worker-1");
        Thread t2 = new Thread(task, "Worker-2");
        t1.start();
        t2.start();
        // 輸出:counter 從 5 遞減到 0,兩線程共享同一變量
    }
}

3.2 核心優勢

  • 任務與執行分離:Runnable 只負責「做什麼」,Thread 負責「怎麼跑」
  • 支持資源共享:同一 Runnable 實例可被多個 Thread 共享,實現賣票、生產者消費者等經典模型
  • 無繼承限制:任務類可繼承其他業務基類
  • 兼容線程池:ExecutorService.execute(Runnable)、FutureTask、CompletableFuture 均接受 Runnable
  • 支持 Lambda:() -> { ... } 直接作為 Runnable

3.3 線程對象 vs 任務對象 vs 當前線程


MyTask task = new MyTask();
Thread t = new Thread(task);

// 三個關鍵對象區別:
// 1. task:任務邏輯載體,run() 內部 this = task
// 2. t:線程控制對象,start()/join()/interrupt() 針對 t
// 3. Thread.currentThread():正在運行代碼的線程
//    - main 調用 t.start() 時:currentThread() = main
//    - t 運行 task.run() 時:currentThread() = t

四、資源共享性深度對比

4.1 不共享模式(繼承 Thread / 多個 Runnable 實例)


// 每個線程獨立計數器
class IndependentThread extends Thread {
    private int count = 5;
    public void run() { while (count-- > 0) print(); }
}
new IndependentThread().start(); // count=5
new IndependentThread().start(); // count=5 (獨立副本)

4.2 共享模式(單一 Runnable 實例)


class SharedTask implements Runnable {
    private int count = 5; // 共享變量
    public void run() { 
        while (true) {
            synchronized(this) { // 必須同步
                if (count <= 0) break;
                System.out.println(Thread.currentThread().getName() + " sold: " + count--);
            }
        }
    }
}
SharedTask task = new SharedTask();
new Thread(task, "Window-1").start();
new Thread(task, "Window-2").start();
// 輸出:5,4,3,2,1 無重複無遺漏(需 synchronized 保證原子性)

4.3 線程安全補充

共享模式下,count-- 非原子(讀-改-寫),必須加鎖:

  • synchronized(this) 鎖任務實例
  • AtomicInteger 無鎖 CAS
  • ReentrantLock 顯式鎖

五、線程啟動與生命週期


Thread t = new Thread(() -> { /* task */ });

t.start();  // 【關鍵】啟動新線程,異步執行 run()
// t.run();  // 【錯誤】直接調用,同步在主線程執行,不創建新線程!

// 生命週期狀態
Thread.State state = t.getState();
// NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED

六、生產環境選型建議

場景推薦方式理由
業務邏輯任務Runnable / Callable + 線程池任務復用、隊列緩衝、拒絕策略、監控指標
簡單定時/周期任務ScheduledExecutorService / Spring @Scheduled內置調度、彈性配置
需要返回值/異常Callable + Future / CompletableFuture獲取結果、異常傳播、組合編排
虛擬線程 (JDK 21+)Thread.ofVirtual().start(Runnable)輕量級、百萬併發、無需池化
教學/原型驗證Thread 匿名內部類 / Lambda代碼極簡

七、總結

  1. 本質相同:兩種方式最終都創建 Thread 實例,調用 start0() 請求 OS 創建線程
  2. 核心差異:繼承 Thread = 任務綁定線程;實現 Runnable = 任務解耦線程
  3. 資源共享:單一 Runnable 實例 + 多 Thread = 共享狀態(需同步)
  4. 工程規範:禁止直接 new Thread,統一使用線程池,命名規範 biz-thread-pool-%d
  5. 未來趨勢:JDK 21 虛擬線程將改寫併發模型,Thread.startVirtualThread(() -> {}) 成主流

理解「任務」與「線程」的分離,是寫出可維護、可擴展、高性能併發代碼的基石。

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