Java Swing多線程死鎖問題解析
原創(chuàng)【51CTO獨(dú)家特稿】在基于Java Swing進(jìn)行圖形界面開發(fā)的時候,經(jīng)常遇到的就是Swing多線程問題。我們可以想想一下,如果需要在一個圖形界面上顯示很多數(shù)據(jù),這些數(shù)據(jù)是經(jīng)過長時間、復(fù)雜的查詢和運(yùn)算得到的。如果在圖形界面的同一個線程中進(jìn)行查詢和運(yùn)算工作則會導(dǎo)致一段時間界面處于死機(jī)狀態(tài),這會給用戶帶來不良的互動感受。為了解決這個問題,一般會單獨(dú)啟動一個線程進(jìn)行運(yùn)算和查詢工作,并隨時更新圖形界面。這時候,另一個問題就出現(xiàn)了,可能不僅沒有解決原來偶爾死機(jī)問題,還可能導(dǎo)致程序徹底死掉。幸運(yùn)的是在JDK中暗藏了一個中斷程序的快捷鍵,就是CTRL+BREAK,這個快捷鍵Sun并沒有在文檔中公布。如果在命令行模式下啟動Java程序,然后按CTRL+BREAK鍵,會得到堆棧的跟蹤信息。從這些跟蹤信息中就可以知道具體引發(fā)死機(jī)的位置了。
當(dāng)一個程序產(chǎn)生死鎖的時候,你一定會希望盡快找到原因并且解決它。這時候,你一般的精力會用在查找引發(fā)死鎖的位置,另一半的精力會用于對堆棧進(jìn)行跟蹤一確定引發(fā)死鎖的原因。但是在Java Swing程序中,你的所有努力可能都是沒有價值的。這是因?yàn)镴ava對Swing的多線程編程有一個特殊要求。就是在Swing里,只能在與Swing相同的線程里對GUI元件進(jìn)行修改。
也就是說,如果你要執(zhí)行類似于jLabel1.setText("blabla")代碼,必須在Swing線程中,而不允許在其他線程當(dāng)中。如果必須在其他線程中修改元件,可以使用類似一下方式解決:
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- jLabel1.setText("blabla");
- }
- }
invokeLater方法雖然表面有時間延遲執(zhí)行含義,但是實(shí)際上幾乎沒有任何影響,可能在幾毫秒之內(nèi)就會被執(zhí)行。另外還有一個invokeAndWait方法,除非特殊需要,否則幾乎是不用的。
在不使用invokeLater的情況下,導(dǎo)致刷新問題是可以理解的,但是導(dǎo)致死鎖就優(yōu)點(diǎn)令人匪夷所思了。幸運(yùn)的是,不是任何時候都需要調(diào)用改方法,這是因?yàn)榇蠖鄶?shù)情況下,我們都是在與Swing同一個線程里進(jìn)行界面更新。例如監(jiān)聽按鈕點(diǎn)擊事件的ActionListener.actionPerformed方法就是運(yùn)行在與Swing相同的線程中的。但是如果在回調(diào)類中引用了另一個類,并且是不屬于AWT/Swing的,那么結(jié)果就很難確定了。所以說使用invokeLater應(yīng)該是最安全的。
需要注意的是,在invokeLater做的任何事情,都會導(dǎo)致Swing線程窗口繪制工作暫停下來,等候invokeLater工作結(jié)束。所以不要在invokeLater進(jìn)行耗時操作,盡量只執(zhí)行那些界面繪制相關(guān)的工作。可以通過代碼重構(gòu),將那些與界面更新相關(guān)的代碼集中起來統(tǒng)一處理。
一個建議是那些在Swing中使用的類進(jìn)行合理的設(shè)計。代碼執(zhí)行前判斷是否處于Swing線程當(dāng)中(使用SwingUtilities.isEventDispatchThread()方法),如果不是,則需要通過SwingUtilities.invokeLater(Runnable)執(zhí)行,否則則直接執(zhí)行代碼。但是這說起來簡單,但是實(shí)際操作會遇到很多困難。
【編輯推薦】