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

Java 多線程入門實戰:定時炸彈與兩種線程實現方式對比

     編輯:Java 相關知識

場景:一個可中斷的倒計時器

我們要實現一個「定時炸彈」:程式啟動後開始 60 秒倒計時,每秒印出剩餘秒數;使用者可在控制台輸入 quit 隨時拆彈(停止線程)。這需要兩條線程並行:

  • 主線程:阻塞在 readLine() 等待使用者輸入。
  • 炸彈線程:每秒遞減計數器,歸零則爆炸。

方案一:繼承 Thread 類

炸彈線程類

public class TimeBombThread extends Thread {
    private int countdown = 60;
    private volatile boolean running = true; // volatile 保證可見性
    
    public TimeBombThread() { start(); } // 建構子中啟動
    
    @Override
    public void run() {
        try {
            while (running) {
                Thread.sleep(1000);
                System.out.println("剩餘時間:" + countdown);
                if (countdown <= 0) {
                    System.out.println("炸彈爆炸!");
                    break;
                }
                countdown--;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢復中斷狀態
        }
    }
    
    // 安全停止線程的標準姿勢:標誌位 + interrupt
    public void stopThread() {
        running = false;
        interrupt(); // 打斷 sleep 讓線程立即醒來檢查標誌
    }
}

主線程測試類

import java.io.*;

public class TestTimeBomb1 {
    public static void main(String[] args) {
        TimeBombThread bomb = new TimeBombThread(); // 啟動炸彈線程
        
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        try {
            while (true) {
                System.out.print("輸入 quit 結束線程:");
                line = br.readLine(); // 阻塞等待輸入
                if ("quit".equals(line)) {
                    bomb.stopThread(); // 通知炸彈線程停止
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("程式結束");
    }
}

方案二:Timer + TimerTask 組合

java.util.Timer 內部維護一條專用線程,負責按時觸發 TimerTask(實作 Runnable)。

炸彈任務類

import java.util.*;

public class TimeBombTimerTask extends TimerTask {
    private int countdown = 60;
    private final Timer timer;
    private volatile boolean running = true;
    
    public TimeBombTimerTask() {
        timer = new Timer();
        timer.schedule(this, 0); // 0 延遲立即啟動
    }
    
    @Override
    public void run() {
        if (!running) return;
        System.out.println("剩餘時間:" + countdown);
        if (countdown <= 0) {
            System.out.println("炸彈爆炸!");
            stopThread();
            return;
        }
        countdown--;
    }
    
    public void stopThread() {
        running = false;
        timer.cancel(); // 取消 Timer,釋放其內部線程
    }
}

主線程測試類(幾乎相同)

public class TestTimeBomb2 {
    public static void main(String[] args) {
        TimeBombTimerTask bomb = new TimeBombTimerTask();
        // ... 同樣的 BufferedReader 迴圈讀取 quit ...
    }
}

兩種方式深度對比

維度繼承 ThreadTimer + TimerTask
本質自定義線程類,完全控制 run() 邏輯定時調度框架,適合週期性/延遲任務
啟動方式new 實例後呼叫 start()Timer.schedule(task, delay/period)
停止方式標誌位 + interrupt()task 結束 + timer.cancel()
適用場景長期運行、邏輯複雜的背景線程定時備份、心跳檢測、週期性清理
缺點單一職責較重,繼承限制單線程串行執行多任務,長任務會阻塞後續任務

核心知識點補充

1. 為什麼用 volatile boolean running?

保證主線程修改 running = false 後,炸彈線程下一次讀取能立即看到新值,避免 CPU 緩存導致的可見性問題。

2. 為什麼要呼叫 interrupt()?

線程在 sleep() 時無法檢查標誌位。interrupt() 會拋出 InterruptedException 打斷睡眠,讓線程立即進入 catch 區塊、檢查標誌、優雅退出。

3. Timer 的陷阱

  • 單線程執行所有 Task,若某 Task 耗時過長,會延遲後續 Task。
  • 異常會導致 Timer 線程終止,所有已排程 Task 永不再執行。
  • 現代替代方案:ScheduledExecutorService(線程池、異常隔離、更靈活)。

總結

「定時炸彈」雖簡單,卻完整包含了線程建立、啟動、協作(共享標誌位)、阻塞等待、安全終止等核心要素。繼承 Thread 適合邏輯獨立的長期線程;Timer 適合簡單定時任務。實際專案中,請優先考慮 ExecutorService 與 CompletableFuture 等高層抽象。

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