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

Java 多線程實現方式深度對比:Thread 繼承 vs Runnable 實現

     編輯:Java 相關知識

前言

多線程是 Java 並發編程基石。Java 提供兩種創建線程的標準途徑:繼承 Thread 類與實現 Runnable 接口。本文從原理、代碼結構、資源共享、擴展性四個維度深度對比,給出明確選型建議。

線程生命週期速覽

NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED

  • start():啟動線程,JVM 分配資源,異步執行 run()
  • run():直接同步調用,不創建新線程(常見錯誤)
  • 線程只能 start() 一次,重複調用拋 IllegalThreadStateException

方式一:繼承 Thread 類

class MyThread extends Thread {
    private String name;
    public MyThread(String name) { this.name = name; }
    
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + " → 第" + i + "次執行");
        }
    }
}

public class ExtendThreadDemo {
    public static void main(String[] args) {
        new MyThread("線程A").start();
        new MyThread("線程B").start();
        new MyThread("線程C").start();
    }
}

特點:

  • 每個線程對應一個 MyThread 實例
  • 線程邏輯與線程控制耦合在同一類
  • 缺陷:Java 單繼承限制,無法繼承其他業務類

方式二:實現 Runnable 接口(推薦)

class Task implements Runnable {
    private int ticket = 100; // 共享資源
    
    @Override
    public void run() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + " 賣出第 " + ticket-- + " 張票");
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        Task task = new Task(); // 單一任務實例
        new Thread(task, "窗口A").start();
        new Thread(task, "窗口B").start();
        new Thread(task, "窗口C").start();
    }
}

優勢:

  • 資源天然共享:同一 Task 實例被多線程引用,ticket 自動共享
  • 解耦:任務邏輯與線程控制分離,支援線程池、CompletableFuture 等高級 API
  • 無單繼承限制:可同時繼承業務基類
  • 支援 Lambda:new Thread(() -> System.out.println("Hi")).start()

核心差異對比表

維度繼承 Thread實現 Runnable
代碼復用差(邏輯綁定線程)優(任務獨立)
資源共享需額外傳遞共享對象天然共享同一實例
繼承限制佔用單繼承名額無影響
線程池適配不支援直接提交完美支援 execute(runnable)
Lambda 表達式不支援完美支援
適用場景簡單獨立線程、教學演示所有生產級並發場景

進階:Callable + FutureTask 獲取返回值

Runnable 無返回值,需結果時用 Callable:

public class CallableDemo {
    public static void main(String[] args) throws Exception {
        Callable task = () -> {
            Thread.sleep(1000);
            return 42;
        };
        
        FutureTask future = new FutureTask<>(task);
        new Thread(future).start();
        
        System.out.println("主線程繼續工作...");
        System.out.println("任務結果: " + future.get()); // 阻塞獲取結果
    }
}

線程池最佳實踐(生產環境標準)

// 避免直接 new Thread,使用線程池
ExecutorService pool = Executors.newFixedThreadPool(4); // 或自定義 ThreadPoolExecutor

// 提交 Runnable
pool.execute(() -> System.out.println("無返回值任務"));

// 提交 Callable 獲取 Future
Future future = pool.submit(() -> {
    Thread.sleep(500);
    return "有返回值";
});

try {
    System.out.println(future.get(2, TimeUnit.SECONDS));
} catch (TimeoutException e) {
    future.cancel(true);
} finally {
    pool.shutdown(); // 優雅關閉
}

常見陷阱與避坑

  1. 直接調用 run():不會創建新線程,主線程同步執行
  2. 共享變量非線程安全:ticket-- 非原子,需 AtomicInteger 或 synchronized
  3. 線程池未關閉:JVM 無法正常退出,必須 shutdown()
  4. 異常吞沒:線程內拋出異常不會傳播到主線程,需 Thread.setDefaultUncaughtExceptionHandler
  5. 過度創建線程:導致 OOM,永遠使用有界線程池

總結

一律使用 Runnable/Callable + 線程池。繼承 Thread 僅適合教學驗證。現代 Java 並發編程核心是「任務提交給執行器」,而非「手動管理線程」。掌握 ExecutorService、CompletableFuture、parallelStream 才是生產級技能。

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