2008-10-08 216 views
22

我經常聽到對Swing庫中線程安全性缺乏的批評。然而,我不確定我在自己的代碼中會做什麼會導致問題:Java:Swing庫和線程安全

在什麼情況下,Swing不是線程安全的?

我應該主動避免做什麼?

回答

26
  1. 從不做長時間運行的任務來響應按鈕,事件等,因爲它們在事件線程中。如果阻塞事件線程,則整個GUI將完全無響應,從而導致非常生氣的用戶。這就是爲什麼Swing看起來很慢而且很硬。

  2. 使用線程,執行程序和SwingWorker來運行不在EDT(事件調度線程)上的任務。

  3. 請勿在EDT之外更新或創建窗口小部件。在美國東部以外地區唯一可以做的就是Component.repaint()。使用SwingUtilitis.invokeLater確保在EDT上執行某些代碼。

  4. 使用EDT Debug Techniques和一個聰明的外觀和感覺(如Substance,其檢查EDT違規)

如果你遵循這些規則,鞦韆可以做一些非常有吸引力的,反應的GUI

的一些非常棒的Swing UI工作示例:​​。注意:我不爲他們工作,只是一個令人敬畏的揮杆的例子。恥辱沒有公開演示...他們的blog也不錯,稀疏但很好

+1

還有一個invokeAndWait()方法,但儘可能使用invokeLater()。 – Powerlord 2008-10-08 12:16:55

+1

如果您想要死鎖,請使用invokeAndWait。 ;) – 2008-10-08 13:03:18

+0

或按小時支付。 :-) – 2008-10-08 15:14:55

3

除了事件派發線程之外,主動避免執行任何Swing工作。 Swing寫得很容易擴展,Sun決定使用單線程模型更好。

我沒有問題,同時遵循我的建議。在某些情況下,你可以從其他線程「擺動」,但我從來沒有找到需要。

8

這不僅僅是Swing不是線程安全的(不是很多),但它是線程敵對的。如果你開始在單線程(EDT之外)上執行Swing東西,那麼當Swing切換到EDT(未記錄)時,可能會出現線程安全問題。即使Swing文本的目標是線程安全的,也不是線程安全的(例如,追加到文檔中,您首先需要查找長度,在插入之前可能會更改該長度)。

因此,在EDT上執行所有Swing操作。注意EDT不是線程的主叫,所以啓動(簡單)Swing應用程序這樣的樣板:

class MyApp { 
    public static void main(String[] args) { 
     java.awt.EventQueue.invokeLater(new Runnable() { public void run() { 
      runEDT(); 
     }}); 
    } 
    private static void runEDT() { 
     assert java.awt.EventQueue.isDispatchThread(); 
     ... 
2

如果您使用的是Java 6的SwingWorker那麼肯定是要對付這種最簡單的方法。

基本上你想確保在EventDispatchThread上執行任何改變UI的東西。

這可以通過使用SwingUtilities.isEventDispatchThread()方法來告訴你,如果你在它(通常不是一個好主意 - 你應該知道什麼線程是活動的)。

如果您不在EDT上,那麼您可以使用SwingUtilities.invokeLater()和SwingUtilities.invokeAndWait()來調用EDT上的Runnable。

如果你更新UI上的不在EDT上,你會得到一些令人難以置信的奇怪行爲。就我個人而言,我不認爲這是Swing的缺陷,你不需要同步所有線程來提供UI更新,就可以獲得一些很好的效率 - 你只需要記住那個警告。

11

這是讓我很高興的其中一個問題,我購買了Robinson & Vorobiev's book on Swing

凡是訪問java.awt.Component的狀態應該在美國東部時間內運行,有三個例外:任何具體的記錄爲線程安全的,如repaint()revalidate()invalidate(); UI中的任何組件尚未被實現;以及Applet的start()之前的Applet中的任何組件。

專門製作線程安全的方法非常罕見,通常只需記住那些方法就足夠了;您通常也可以假設沒有這樣的方法(例如,將重繪調用包裝在SwingWorker中是完全安全的)。

實現意味着該組件可以是一個頂層容器(像的JFrame),其上的setVisible(true)show(),或任何pack()已被調用,或者它已被添加到一個實現組件。這意味着在main()方法中構建用戶界面非常好,正如很多教程示例所做的那樣,因爲在頂級容器上不會調用setVisible(true),直到每個組件都已添加到它,配置了字體和邊框等等

由於類似的原因,在init()方法中構建小應用程序用戶界面非常安全,並且在構建完所有方法後再調用start()

包裝隨後的組件更改發送到invokeLater()的Runnables變得很容易,只需幾次即可完成。我發現惱人的一件事是從另一個線程讀取組件的狀態(例如,someTextField.getText())。從技術上講,這也必須包含在invokeLater()中;在實踐中,它可以使代碼變得很難,而且我經常不打擾,或者我在第一次事件處理的時候(通常是在大多數情況下通常是正確的時候這樣做)抓緊這些信息。

1

這是一個模式,可以讓線程自由擺動。

子類動作(MyAction)並使其成爲doAction線程。 使構造函數採用字符串名稱。

給它一個抽象actionImpl()方法。

讓它看起來像.. (僞警告!)

doAction(){ 
new Thread(){ 
    public void run(){ 
    //kick off thread to do actionImpl(). 
     actionImpl(); 
     MyAction.this.interrupt(); 
    }.start(); // use a worker pool if you care about garbage. 
try { 
sleep(300); 
Go to a busy cursor 
sleep(600); 
Show a busy dialog(Name) // name comes in handy here 
} catch(interrupted exception){ 
    show normal cursor 
} 

可以錄製下一次拍攝任務的時間,而且,你的對話框可以顯示一個體面的估計。

如果你想真的很好,也可以在另一個工作線程中睡覺。

2

短語'線程不安全'聽起來像是有些天生不好的東西(你知道......'安全' - 好;'不安全' - 壞)。現實情況是,線程安全的代價是 - 線程安全的對象通常實現起來更加複雜(並且Swing足夠複雜,即使是這樣)。

此外,線程安全性可以使用鎖定(緩慢)或比較和交換(複雜)策略。鑑於GUI與人類交互,這往往是不可預知的並且難以同步,許多工具包已決定通過單個事件泵來引導所有事件。這對於Windows,Swing,SWT,GTK以及其他人來說都是如此。實際上,我不知道一個真正的線程安全的GUI工具包(這意味着你可以從任何線程操縱其對象的內部狀態)。

通常做的是,GUI提供了一種處理線程不安全的方法。正如其他人所指出的那樣,Swing總是提供了一些簡單的SwingUtilities.invokeLater()。 Java 6包含優秀的SwingWorker(可用於Swinglabs.org的以前版本)。還有Foxtrot等第三方庫管理Swing上下文中的線程。

Swing的惡名是因爲設計師採取了輕率的方法,假設開發人員會做正確的事情,而不是停止EDT或修改EDT以外的組件。他們已經明確表示了他們的線程策略,並且要由開發人員來遵循。

讓每個揮杆API向EDT發佈每個屬性集合,無效等等,這將使其線程安全,但代價是大幅度減速,這是微不足道的。你甚至可以使用AOP自己做。爲了比較,SWT在從一個錯誤的線程訪問一個組件時拋出異常。

1

請注意,即使模型接口都不是線程安全的。大小和內容使用單獨的get方法查詢,因此無法同步這些方法。

從另一個線程更新模型的狀態允許它至少繪製尺寸仍然較大(錶行仍在原地)的情況,但內容不再存在。

始終在EDT中更新模型的狀態可避免這些情況。

1

當您與任何非EDT EDT線程的GUI組件進行任何交互時,都必須使用invokeLater()和invokeAndWait()。

它可能在開發過程中工作,但像大多數併發錯誤一樣,您會看到奇怪的異常出現,看起來完全不相關,並且非確定性地發生 - 通常在真實用戶發貨後發現。不好。另外,你還沒有信心,你的應用程序將繼續在越來越多的內核的未來CPU上工作 - 由於它們是真正的併發而不是僅僅由操作系統模擬的,所以更容易遇到奇怪的線程問題。

是的,它變得醜陋,每一個方法回調到一個Runnable實例的EDT中,但這對你來說是Java。在我們關閉之前,你只能忍受它。

4

使用智能皮膚類物質的替代方法是創建下面的實用方法:

public final static void checkOnEventDispatchThread() { 
    if (!SwingUtilities.isEventDispatchThread()) { 
     throw new RuntimeException("This method can only be run on the EDT"); 
    } 
} 

調用它的每一個方法你寫的要求是事件調度線程上。這樣做的一個優點是可以快速禁用和啓用全系統檢查,例如可能會在生產中將其刪除。

注意智能皮膚當然可以提供額外的覆蓋範圍以及這一點。