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

深入淺出 Java 多線程(二):Swing EDT 與單實例鎖的正確姿勢

     編輯:Java 相關知識

問題背景:如何保證 Swing 程式單實例運行?

Java 程式由 main 方法啟動進程。要確保同一時間只有一個進程運行,常見做法是利用系統級資源鎖。考慮跨平台性,作者採用 JDK 1.4 NIO 引入的 FileLock:程式啟動時嘗試對特定檔案加獨佔鎖,成功則持有鎖繼續運行;失敗則說明已有實例在運行,直接退出。

初嘗試:將鎖邏輯放入 EDT 導致介面凍結

Swing 程式啟動後,main 執行緒結束,事件分發執行緒(EDT)接管 UI 迴圈。作者嘗試用 SwingUtilities.invokeAndWait() 將加鎖邏輯提交給 EDT 執行:

SwingUtilities.invokeAndWait(new getFileLock());

結果 UI 完全無回應。打斷點發現 getFileLock.run() 確實在 AWT-EventQueue-0(即 EDT)執行,且因 Thread.sleep(Integer.MAX_VALUE) 永久阻塞。EDT 是單執行緒,負責處理所有 UI 事件(繪製、滑鼠、鍵盤),一旦被長時間任務佔用,整個介面凍結。

EDT 運作機制深度解析

生產者-消費者模型

  • 生產者:任意執行緒(含 main)透過 invokeLater/invokeAndWait 將 Runnable 封裝為 InvocationEvent 放入 EventQueue。
  • 消費者:EDT 不斷從 EventQueue 取事件,呼叫 run() 執行。

invokeAndWait vs invokeLater

// invokeLater:非阻塞,塞入隊列即返回
public static void invokeLater(Runnable r) {
    Toolkit.getEventQueue().postEvent(new InvocationEvent(..., r));
}

// invokeAndWait:阻塞呼叫執行緒,直到事件處理完畢
public static void invokeAndWait(Runnable r) throws ... {
    Object lock = new AWTInvocationLock();
    InvocationEvent event = new InvocationEvent(..., r, lock, true);
    synchronized (lock) {
        Toolkit.getEventQueue().postEvent(event);
        lock.wait();  // 當前執行緒(如 main)在此等待
    }
    // EDT 處理完後會 notifyAll 喚醒
}

關鍵差異:invokeAndWait 會阻塞呼叫執行緒,invokeLater 不會。但兩者最終都在 EDT 同步執行 Runnable.run(),絕不可在其中執行耗時/阻塞操作。

正確解法:獨立 Daemon 執行緒持有鎖

將加鎖邏輯移出 EDT,在 main 中啟動一條 Daemon 執行緒專責持有檔案鎖:

public static void main(String[] args) {
    // ... UI 初始化 ...
    Thread t = new Thread(new getFileLock());
    t.setDaemon(true);  // 隨 JVM 退出而結束
    t.start();
    // ... 顯示 JFrame ...
}

class getFileLock implements Runnable {
    public void run() {
        RandomAccessFile r = new RandomAccessFile("d://testData.java", "rw");
        FileLock fl = r.getChannel().tryLock();
        if (fl == null) System.exit(1);  // 拿不到鎖,直接退出
        Thread.sleep(Integer.MAX_VALUE); // 持有鎖直到程式結束
    }
}

此法優點:
1. 不阻塞 EDT,UI 保持流暢。
2. Daemon 執行緒不阻止 JVM 退出。
3. tryLock() 非阻塞,立即得知能否獲鎖。
4. 鎖隨程式生命週期自動釋放(檔案關閉/進程結束)。

總結

  • EDT 是單執行緒事件迴圈,專司 UI 更新與事件分發。
  • invokeAndWait/invokeLater 只是將任務排入事件隊列,執行緒仍是 EDT。
  • 任何長時間運算、阻塞 I/O、持有鎖等操作嚴禁放入 EDT。
  • 單實例鎖、資料庫連線、網路輪詢等背景任務,應另闢執行緒(建議 Daemon)處理。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved