Java 程式由 main 方法啟動進程。要確保同一時間只有一個進程運行,常見做法是利用系統級資源鎖。考慮跨平台性,作者採用 JDK 1.4 NIO 引入的 FileLock:程式啟動時嘗試對特定檔案加獨佔鎖,成功則持有鎖繼續運行;失敗則說明已有實例在運行,直接退出。
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 事件(繪製、滑鼠、鍵盤),一旦被長時間任務佔用,整個介面凍結。
main)透過 invokeLater/invokeAndWait 將 Runnable 封裝為 InvocationEvent 放入 EventQueue。EventQueue 取事件,呼叫 run() 執行。// 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(),絕不可在其中執行耗時/阻塞操作。
將加鎖邏輯移出 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. 鎖隨程式生命週期自動釋放(檔案關閉/進程結束)。
invokeAndWait/invokeLater 只是將任務排入事件隊列,執行緒仍是 EDT。