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

Java Applet 核心技能 (五):多執行緒原理、生命週期與時鐘 Applet 實戰

     編輯:Java 相關知識

前言:為什麼 Applet 必須掌握多執行緒?

Applet 運行在瀏覽器進程中,主執行緒負責 UI 繪製與事件分發。若在 paint() 或事件處理器中執行耗時操作 (網絡下載、複雜計算、睡眠),會凍結整個瀏覽器頁面甚至導致「未響應」。多執行緒將耗時邏輯剝離至背景執行緒,主執行緒保持流暢響應,是 Applet (及所有 GUI 程式) 的生存法則。

一、核心概念釐清

術語定義類比
程式靜態代碼文件食譜
進程程式的一次動態執行,擁有獨立記憶體空間廚房 (含原料、工具、廚師)
執行緒進程內更小的執行單位,共享進程資源 (堆、方法區),擁有獨立棧、PC 計數器廚師 (可同時多位廚師在同一廚房工作)

二、Java 執行緒五大狀態與轉換

新建 → 啟動 → 就緒 ↔ 運行 → 阻塞 → 死亡
  ↑_____________釋放鎖/資源就緒_____________|
狀態觸發條件典型方法
新建new Thread()構造器
就緒start()、阻塞解除、時間片用盡進入排隊區
運行獲得 CPU 時間片執行 run() 體
阻塞睡眠、等待 I/O、等待鎖、wait()/join()sleep(), wait(), read()
死亡run() 正常結束、stop()/destroy() 強制 (已棄用)自然結束或拋出未捕獲異常

關鍵:start() 啟動新執行緒 (異步);直接呼叫 run() 仍在當前執行緒同步執行。

三、範例 1:兩個獨立計數執行緒

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);
    }
}

觀察兩計數器以不同節奏遞增,驗證執行緒並發性。

四、範例 2:類比時鐘 Applet (完整代碼解析)

設計要點

  • 主類 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);
    }
}

關鍵技術點

  1. 三角函數計算指針端點:x + len*sin(θ), y - len*cos(θ) (y 軸向下)。
  2. 時針連續轉動:公式包含分秒折算,非跳躍式。
  3. 執行緒安全停止:教學用 stop(),生產環境嚴禁。正確模式:
    volatile boolean running = true;
    public void run() {
        while (running) { ... }
    }
    public void stop() { running = false; clockThread.interrupt(); }
  4. repaint() 非同步:只排程重繪,不立即執行 paint(),避免阻塞事件執行緒。

五、現代化對照:JavaFX / Swing 定時器

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 → ✅ 捕獲後檢查中斷標誌、清理資源、退出循環。
  • ❌ UI 操作在背景執行緒 → ✅ Swing: SwingUtilities.invokeLater();JavaFX: Platform.runLater();Applet: repaint() 本身線程安全。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved