通過一個完整的 Applet 動畫範例,掌握:
MediaTracker 同步機制Thread.join() 優雅停止volatile 關鍵字在多線程可見性中的關鍵作用
import javax.swing.JApplet;
import java.awt.*;
import java.awt.image.ImageObserver;
import java.awt.MediaTracker;
public class Animate extends JApplet {
private static final int NUM_OF_PIC = 4;
private int count = 1;
private Image[] pics = new Image[NUM_OF_PIC];
private TimerThread timer;
public void init() {
MediaTracker tracker = new MediaTracker(this);
for (int i = 0; i < NUM_OF_PIC; i++) {
// 圖片命名 0.jpg, 1.jpg... 放在 codebase 目錄
pics[i] = getImage(getCodeBase(), i + ".jpg");
tracker.addImage(pics[i], 0);
}
try { tracker.checkAll(true); } // 等待所有圖片載入完成
catch (InterruptedException e) { e.printStackTrace(); }
}
public void start() {
timer = new TimerThread(this, 1000); // 1 秒切換一次
timer.start();
}
public void stop() {
timer.shouldRun = false; // 通知線程退出
try { timer.join(); } // 等待線程真正結束
catch (InterruptedException e) {}
}
public void paint(Graphics g) {
g.drawImage(pics[count++], 0, 0, this);
if (count == NUM_OF_PIC) count = 0;
}
}
import java.awt.Component;
public class TimerThread extends Thread {
private Component comp;
private int timediff;
public volatile boolean shouldRun = true; // 關鍵:volatile 保證可見性
public TimerThread(Component comp, int timediff) {
super("TimerThread-" + timediff);
this.comp = comp;
this.timediff = timediff;
}
public void run() {
while (shouldRun) {
try {
comp.repaint(); // 觸發 paint()
sleep(timediff);
} catch (Exception e) { break; }
}
}
}
getImage() 非阻塞立即返回,圖片實際載入在背景進行。直接使用會導致繪製空白或不完整。
三步驟標準流程:
MediaTracker tracker = new MediaTracker(component);tracker.addImage(image, id); (id 可分組管理)tracker.checkAll(true); 阻塞等待全部載入完成(true 表示遇錯誤拋出異常)。join():當前線程阻塞,直到目標線程結束。CPU 佔用極低,響應即時。
輪詢替代方案:
while (timer.isAlive()) {
try { Thread.sleep(50); } catch (InterruptedException e) {}
}
缺點:浪費 CPU、引入額外延遲(最多 50ms)、代碼冗長。強烈推薦 join()。
背景:JVM 1.2+ 引入線程本地緩存(寄存器、L1/L2 Cache),線程讀寫變量可能不直接操作主記憶體。
問題:主線程修改 shouldRun = false,動畫線程可能仍讀取舊的緩存值 true,導致無法停止。
解決:volatile boolean shouldRun 強制每次讀寫直接操作主記憶體,確保所有線程立即看到最新值。
規則:多線程共享的標誌位、狀態變量,必須加volatile(或使用AtomicBoolean/同步塊)。
init() 載入 4 張圖片並阻塞等待完成。start() 啟動 TimerThread,每秒呼叫 repaint()。paint() 循環繪製圖片,實現動畫。stop() 設 shouldRun=false → join() 等待線程安全退出 → 資源釋放。Applet 技術已淘汰(瀏覽器不再支援 Java 插件)。現代替代方案:
SwingWorker 處理背景任務。但本例展示的多線程協作、資源同步、記憶體可見性核心概念,在所有 Java 併發編程中通用。