天天看點

Swing 的任務線程與 EDT 事件分發隊列模型(下)6 Swing 事件分發線程(EDT)7 Swing不是一個“安全線程”的API,為什麼要這樣設計8 invoke系方法

6 Swing 事件分發線程(EDT)

Swing的事件隊列就類似事件隊列,僅單一消費者,即一個事件分發線程。

除非你的程式停止,否則EDT會永不間斷地徘徊在處理請求與等待請求之間。

Swing事件隊列的實作機制圖解

Swing 的任務線程與 EDT 事件分發隊列模型(下)6 Swing 事件分發線程(EDT)7 Swing不是一個“安全線程”的API,為什麼要這樣設計8 invoke系方法

6.1 單一線程的事件隊列的特性

  • 将同步操作轉為異步操作
  • 将并行處理轉換為串行順序處理

6.2 EDT要處理所有GUI操作

  • 職責明确,任何GUI請求都應該在EDT中調用
  • 要處理的GUI請求非常多,包括視窗移動、元件自動重繪、重新整理,它很忙。任何與GUI無關的處理不要由EDT執行,尤其是I/O耗時操作

7 Swing不是一個“安全線程”的API,為什麼要這樣設計

Swing的線程安全不是靠自身元件的API來保障,雖然repaint方法是這樣,但是大多數SwingAPI是非線程安全的,也就是說不能在任意地方調用,它應該隻在EDT中調用。Swing的線程安全靠事件隊列和EDT保證。

8 invoke系方法

對非EDT的并發調用需通過invokeLater()和invokeAndWait()使請求插入到隊列中等待EDT去執行。

由于Swing本身非線程安全,如果你在其他線程通路和修改GUI元件,必須使用

8.1 SwingUtilities. invokeAndWait(runnable)

Swing 的任務線程與 EDT 事件分發隊列模型(下)6 Swing 事件分發線程(EDT)7 Swing不是一個“安全線程”的API,為什麼要這樣設計8 invoke系方法

同步的,它被調用結束會立即阻塞目前線程,直到EDT處理完該請求。

一般用于取得Swing元件的資料。

8.2 SwingUtilities. invokeLater(runnable)

使 doRun.run() 在AWT事件分法線程上異步執行。所有待處理的AWT事件被執行後,就會發生這種情況。當應用程式線程需要更新GUI時,應使用此方法。

在下面的示例中,invokeLater調用将Runnable對象doHelloWorld排隊在事件配置設定線程上,然後列印一條消息。

Runnable doHelloWorld = new Runnable() {
     public void run() {
         System.out.println("Hello World on " + Thread.currentThread());
     }
 };

 SwingUtilities.invokeLater(doHelloWorld);
 System.out.println("This might well be displayed before the other message.");      

如果從事件分發線程(例如,從JButton的ActionListener)調用invokeLater,則 doRun.run 仍将延遲,直到處理完所有未決事件。請注意,如果doRun.run 引發未捕獲的異常,則事件分發線程将展開(而不是目前線程)。

從1.3版本開始,此方法隻是java.awt.EventQueue.invokeLater()的封面。

與Swing的其餘部分不同,可以從任何線程調用此方法。

準則

不能在EDT中被調用,否則程式會抛出Error,請求也不會去執行。看源碼:

public static void invokeAndWait(Runnable runnable)
             throws InterruptedException, InvocationTargetException {
 
        //不能在EDT中調用invokeAndWait
        if (EventQueue.isDispatchThread()) {
            throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
        }
 
    class AWTInvocationLock {}
        Object lock = new AWTInvocationLock();
 
        InvocationEvent event = 
            new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock,
                true);
 
        synchronized (lock) {
            //添加進事件隊列
            Toolkit.getEventQueue().postEvent(event);
            //block目前線程
            lock.wait();
        }
 
        Throwable eventThrowable = event.getThrowable();
        if (eventThrowable != null) {
            throw new InvocationTargetException(eventThrowable);
        }
    }      

如果invokeAndWait在EDT中調用,那麼首先将請求壓進隊列,然後EDT便被block,等待請求結束通知它繼續運作。

而實際上請求将永遠得不到執行,因為它在等待隊列的排程使EDT執行它,這就陷入一個僵局:EDT等待請求先執行,請求又等待EDT對隊列的排程。彼此等待對方釋放鎖是造成死鎖的四類條件之一。Swing有意地避免了這類情況的發生。

繼續閱讀