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

Eclipse RCP 客戶端多執行緒開發實戰指南

     編輯:Java 相關知識

Eclipse Rich Client Platform (RCP) 應用日益複雜,恰當運用多執行緒是提升回應速度與使用者體驗的關鍵。Eclipse 在 Java Thread 之上封裝了 Job、UIJob 及執行緒池機制,提供更高層級的並發控制 API。

核心 API 比較

API適用場景特色
Job長時間非 UI 運算、資料庫/網路存取可排程、進度回報、規則控制並發、執行緒池重用
UIJob需在 UI 執行緒更新介面的短任務自動在 UI 執行緒執行 runInUIThread
Display.asyncExec()即時 UI 更新、事件發布非阻塞、排入 UI 事件佇列尾端
Display.syncExec()需等待 UI 更新完成才繼續阻塞呼叫執行緒直到 UI 執行完畢

為何優先使用 Job 而非 Thread

  1. 可重用工作單元:同一 Job 實例可多次 schedule()。
  2. 進度回報標準化:IProgressMonitor 整合進度對話框、取消按鈕。
  3. 並發控制規則:ISchedulingRule 宣告式限制同類 Job 並發數。
  4. 視覺化管理:Progress View 可看所有 Job 狀態、暫停/取消。
  5. 執行緒池效能:Job 完成不銷毀執行緒,回池重用,減少建立/銷毀開銷。

Job 狀態機與管理

JobManager 維護等待佇列與執行緒池。Job 狀態:

  • NONE:新建或已結束
  • WAITING:已排程等待執行
  • RUNNING:正在執行
  • SLEEPING:延遲排程或 sleep() 中

可用 job.getState() 查詢,job.wakeUp() 喚醒睡眠 Job。

UI 執行緒鐵律

  • 所有 Widget 操作(建立、修改、查詢)必須在 UI 執行緒執行,否則拋 SWTException。
  • 判斷當前是否 UI 執行緒:Display.getCurrent() != null。
  • 主迴圈:while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); }

實戰模式範例

1. 控制並發:同類 Job 同一時間只跑一個

ISchedulingRule rule = new ISchedulingRule() {
    public boolean contains(ISchedulingRule r) { return this.equals(r); }
    public boolean isConflicting(ISchedulingRule r) { return this.equals(r); }
};
job1.setRule(rule);
job2.setRule(rule); // job2 會等 job1 結束才跑

2. 條件式執行:避免無意義 Job

UIJob refresh = new UIJob("更新") {
    public IStatus runInUIThread(IProgressMonitor m) { text.setText("新"); return OK; }
    public boolean shouldSchedule() { return !text.isDisposed(); }
    public boolean shouldRun() { return !text.isDisposed(); }
};
refresh.schedule(10000); // 10 秒後執行,若 text 已銷毀則自動取消

3. UI 觸發長任務:背景查詢 + UI 更新

button.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {
        Job job = new Job("查詢資料") {
            protected IStatus run(IProgressMonitor m) {
                final List data = queryDatabase(); // 背景耗時
                Display.getDefault().asyncExec(() -> {
                    tableViewer.setInput(data); // UI 執行緒更新
                });
                return Status.OK_STATUS;
            }
        };
        job.schedule();
    }
});

4. 延遲合併:避免連續選取導致閃爍

Object FAMILY = new Object();
viewer.addSelectionChangedListener(e -> {
    Job.getJobManager().cancel(FAMILY); // 取消前一個待執行 Job
    new UIJob("刷新") {
        protected IStatus runInUIThread(IProgressMonitor m) { updateUI(); return OK; }
        public boolean belongsTo(Object fam) { return fam == FAMILY; }
    }.schedule(500); // 500ms 內無新事件才真正執行
});

5. UI 執行緒等待背景任務完成(不凍結介面)

try {
    ModalContext.run(new IRunnableWithProgress() {
        public void run(IProgressMonitor m) throws InvocationTargetException, InterruptedException {
            fetchRemoteData(); // 非 UI 執行緒跑
            ModalContext.checkCanceled(m);
        }
    }, true, new NullProgressMonitor(), Display.getCurrent());
} catch (Exception ex) { ... }
// 此處 UI 執行緒繼續,且期間介面仍可回應

6. Job Family 批次操作

Object FAMILY = new Object();
job1.setRule(rule); job1.belongsTo(FAMILY); // 實作 belongsTo 回傳 true
// ...
Job.getJobManager().cancel(FAMILY);   // 全取消
Job.getJobManager().join(FAMILY, null); // 全等待
Job.getJobManager().sleep(FAMILY);    // 全睡眠

死鎖診斷與避坑指南

死鎖偵測步驟

  1. Debug 模式重現問題。
  2. Debug View 選中主執行緒 → Run → Suspend → 檢查呼叫堆疊。
  3. 正常 UI 執行緒堆疊頂端通常在 display.readAndDispatch() 或 display.sleep()。
  4. 若頂端是自訂程式碼,檢查正等待何鎖;同步檢查其他執行緒堆疊,找出循環等待鏈。

Job 使用禁忌

  • 禁用 Thread.sleep():會佔住執行緒池執行緒,改用 job.sleep() 釋放執行緒回池。
  • 取消非即時:job.cancel() 僅設旗標,RUNNING 狀態 Job 需自行檢查 monitor.isCanceled() 並提早返回。等待取消完成:if (!job.cancel()) job.join();
  • 慎用 join():易造成死鎖,優先用 ModalContext 或事件驅動設計。
  • 防呆 UI 更新:背景 Job 更新 UI 前必須檢查 widget.isDisposed(),避免使用者關閉視窗後 Job 仍嘗試更新導致例外。

總結

多執行緒能大幅提升 RCP 應用並發能力與使用者體驗,但也帶來複雜度、死鎖風險與效能開銷。原則:能不用就不用、用 Job 別用 Thread、用規則控制並發、用監視器回報進度、用 ModalContext 等待結果。實務上需針對具體場景權衡,本文模式供參考,實際專案請依需求調整。

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