Applet 運行在瀏覽器進程中,主執行緒負責 UI 繪製與事件分發。若在 paint() 或事件處理器中執行耗時操作 (網絡下載、複雜計算、睡眠),會凍結整個瀏覽器頁面甚至導致「未響應」。多執行緒將耗時邏輯剝離至背景執行緒,主執行緒保持流暢響應,是 Applet (及所有 GUI 程式) 的生存法則。
| 術語 | 定義 | 類比 |
|---|---|---|
| 程式 | 靜態代碼文件 | 食譜 |
| 進程 | 程式的一次動態執行,擁有獨立記憶體空間 | 廚房 (含原料、工具、廚師) |
| 執行緒 | 進程內更小的執行單位,共享進程資源 (堆、方法區),擁有獨立棧、PC 計數器 | 廚師 (可同時多位廚師在同一廚房工作) |
新建 → 啟動 → 就緒 ↔ 運行 → 阻塞 → 死亡
↑_____________釋放鎖/資源就緒_____________|
| 狀態 | 觸發條件 | 典型方法 |
|---|---|---|
| 新建 | new Thread() | 構造器 |
| 就緒 | start()、阻塞解除、時間片用盡 | 進入排隊區 |
| 運行 | 獲得 CPU 時間片 | 執行 run() 體 |
| 阻塞 | 睡眠、等待 I/O、等待鎖、wait()/join() | sleep(), wait(), read() |
| 死亡 | run() 正常結束、stop()/destroy() 強制 (已棄用) | 自然結束或拋出未捕獲異常 |
關鍵:start() 啟動新執行緒 (異步);直接呼叫 run() 仍在當前執行緒同步執行。
public class CounterThread extends Applet implements Runnable {
Thread t1, t2;
int count1 = 0, count2 = 0;
public void start() {
if (t1 == null) { t1 = new Thread(this); t1.start(); }
if (t2 == null) { t2 = new Thread(this); t2.start(); }
}
public void run() {
Thread current = Thread.currentThread();
while (true) {
if (current == t1) count1++;
else count2++;
repaint();
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
public void paint(Graphics g) {
g.drawString("Thread 1: " + count1, 10, 20);
g.drawString("Thread 2: " + count2, 10, 40);
}
}
觀察兩計數器以不同節奏遞增,驗證執行緒並發性。
Applet1 實現 Runnable,持有 Thread clockThread。Clock 封裝指針幾何計算與繪製邏輯 (單一職責)。repaint() + sleep(1000)。import java.awt.*;
import java.applet.*;
import java.util.Date;
public class Applet1 extends Applet implements Runnable {
Date timeNow;
Clock myClock;
Thread clockThread = null;
public void start() {
if (clockThread == null) {
clockThread = new Thread(this);
clockThread.start();
}
}
public void stop() {
if (clockThread != null) {
clockThread.stop(); // 教學示範,實際請用 interrupt() + 標誌位
clockThread = null;
}
}
public void run() {
while (true) {
repaint();
try { Thread.sleep(1000); }
catch (InterruptedException e) { break; } // 响應中斷退出
}
}
public void paint(Graphics g) {
timeNow = new Date();
myClock = new Clock(timeNow.getHours(), timeNow.getMinutes(), timeNow.getSeconds());
g.drawString(timeNow.toString(), 25, 240);
myClock.show(g, 100, 100, 100); // 中心 (100,100) 半徑 100
}
}
class Clock {
int hour, minute, second;
Clock(int h, int m, int s) {
hour = h % 12; minute = m; second = s;
}
void show(Graphics g, int cx, int cy, int rad) {
int hrLen = (int)(rad * 0.5);
int minLen = (int)(rad * 0.6);
int secLen = (int)(rad * 0.9);
double theta;
// 鐘面
g.drawOval(cx - rad, cy - rad, rad * 2, rad * 2);
// 時針 (連續轉動)
theta = (hour * 3600 + minute * 60 + second) / 43200.0 * 2 * Math.PI;
drawNeedle(g, Color.blue, cx, cy, hrLen, theta);
// 分針
theta = (minute * 60 + second) / 3600.0 * 2 * Math.PI;
drawNeedle(g, Color.red, cx, cy, minLen, theta);
// 秒針
theta = second / 60.0 * 2 * Math.PI;
drawNeedle(g, Color.green, cx, cy, secLen, theta);
}
private void drawNeedle(Graphics g, Color c, int x, int y, int len, double theta) {
int ex = (int)(x + len * Math.sin(theta));
int ey = (int)(y - len * Math.cos(theta));
g.setColor(c);
g.drawLine(x, y, ex, ey);
}
}
x + len*sin(θ), y - len*cos(θ) (y 軸向下)。stop(),生產環境嚴禁。正確模式:
volatile boolean running = true;
public void run() {
while (running) { ... }
}
public void stop() { running = false; clockThread.interrupt(); }
paint(),避免阻塞事件執行緒。| Applet 時代 | Swing (EDT) | JavaFX (Platform Thread) |
|---|---|---|
Thread + sleep + repaint() | javax.swing.Timer (事件分發執行緒安全) | javafx.animation.AnimationTimer / Timeline |
| 手動管理執行緒生命週期 | Timer 自動隨視窗關閉停止 | 綁定場景圖生命週期 |
stop() 強制終止 | 不可中斷,靠標誌位 | Task/Service 可取消 |
paint() 中建立物件/執行邏輯 → ✅ 僅做繪製,狀態更新放 run()/事件處理器。run() → ✅ 必須 start()。volatile/synchronized/AtomicInteger/ThreadLocal。InterruptedException → ✅ 捕獲後檢查中斷標誌、清理資源、退出循環。SwingUtilities.invokeLater();JavaFX: Platform.runLater();Applet: repaint() 本身線程安全。