我們要實現一個「定時炸彈」:程式啟動後開始 60 秒倒計時,每秒印出剩餘秒數;使用者可在控制台輸入 quit 隨時拆彈(停止線程)。這需要兩條線程並行:
readLine() 等待使用者輸入。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("程式結束");
}
}
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 ...
}
}
| 維度 | 繼承 Thread | Timer + TimerTask |
|---|---|---|
| 本質 | 自定義線程類,完全控制 run() 邏輯 | 定時調度框架,適合週期性/延遲任務 |
| 啟動方式 | new 實例後呼叫 start() | Timer.schedule(task, delay/period) |
| 停止方式 | 標誌位 + interrupt() | task 結束 + timer.cancel() |
| 適用場景 | 長期運行、邏輯複雜的背景線程 | 定時備份、心跳檢測、週期性清理 |
| 缺點 | 單一職責較重,繼承限制 | 單線程串行執行多任務,長任務會阻塞後續任務 |
volatile boolean running?保證主線程修改 running = false 後,炸彈線程下一次讀取能立即看到新值,避免 CPU 緩存導致的可見性問題。
interrupt()?線程在 sleep() 時無法檢查標誌位。interrupt() 會拋出 InterruptedException 打斷睡眠,讓線程立即進入 catch 區塊、檢查標誌、優雅退出。
ScheduledExecutorService(線程池、異常隔離、更靈活)。「定時炸彈」雖簡單,卻完整包含了線程建立、啟動、協作(共享標誌位)、阻塞等待、安全終止等核心要素。繼承 Thread 適合邏輯獨立的長期線程;Timer 適合簡單定時任務。實際專案中,請優先考慮 ExecutorService 與 CompletableFuture 等高層抽象。