Swing多線程編碼過程中的誤區(qū)
很多學(xué)JAVA程序員都是從Swing開始的,但很多人對AWT GUI線程的機(jī)制并沒有太深的了解,或者說一直都只了解線程的概念,而不了解AWT對線程的使用。我發(fā)現(xiàn)很多人碰到線程阻塞的問題,就通過調(diào)用 SwingUtilities.invokeLater()來解決。
其實這是很容易造成誤會的地方:
- 不要以為Swing 是多線程的,實際上Swing 的UI是單線程的
- 不要以為SwingUtilities.的兩個invoke是多線程,實際上它還是單線程的
- 不要以為invokeLater的意思是當(dāng)前線程執(zhí)行完再執(zhí)行目標(biāo)線程;以為invokeAndWait的意思是等待目標(biāo)線程執(zhí)行完再執(zhí)行當(dāng)前線程,實際上壓根就不是那么回事
問題代碼1:大意是在按下某個按鈕的時候調(diào)用一個遠(yuǎn)程服務(wù)
- JButton button = new JButton();
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- invokeRemoteService();//可能需要等待
- }
- });
在swing系統(tǒng)中,有一個頂級的java.awt.Container(可能是一個JFrame或JDialog實例),負(fù)責(zé)啟動一個EventDispatchThread線程,單線程,這個線程是負(fù)責(zé)處理UI事件的。
首先,界面Swing控件向EventDispatchThread的EventQueue提交一個event,由 EventDispatchThread負(fù)責(zé)調(diào)度各個event的執(zhí)行。例如,按下一個JButton的時候,JButton向EventQueue執(zhí)行 postEvent,提交一個ActionEvent。EventDispatchThread線程根據(jù)調(diào)度算法執(zhí)行到該event的時候,會調(diào)用 JButton上的processActionEvent,JButton再調(diào)用actionPerformed,這過程并沒有執(zhí)行任何new Thread().start()代碼,也就是說JButton的ActionListener.actionPerformed()中的代碼完全是在 EventDispatchThread線程內(nèi)執(zhí)行的。
所以,假如我們在任何ActionListener、MouseListener等對象中編寫耗時的邏輯,那么整個Swing系統(tǒng)就會出現(xiàn)響應(yīng)遲鈍的現(xiàn)象,更有甚者,如果在這些Listener中執(zhí)行線程wait(),以等待另一個線程的鎖定資源或計算結(jié)果,那么實際上就是 EventDispatchThread線程被阻塞,整個系統(tǒng)界面就會處于無響應(yīng)狀態(tài),一點(diǎn)反應(yīng)都沒有。
以上是誤解1造成的,了解這個過程,就很容易看出上面這段代碼的問題是什么原因了。解決的方法也倒比較簡單,直接new Thread().start();就可以保證EventDispatchThread執(zhí)行到當(dāng)前方法的時候快速返回,以便可以去響應(yīng)來自用戶界面的其他事件。
問題代碼2:大意是在按下某個按鈕的時候調(diào)用一個遠(yuǎn)程服務(wù),同時處理其他事情
- JButton button = new JButton();
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- //位置A
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- //位置B
- invokeRemoteService();//可能需要等待
- }
- });
- doOtherThing();
- }
- });
這段代碼跟第一段代碼唯一的差別是doOtherThing()在invokeRemoteService ()完成之前就能夠得到執(zhí)行,所以造成了invokeRemoteService ()/doOtherThing()好像是在兩個線程里執(zhí)行的假象。實際上invokeLater是把目標(biāo)代碼打包成一個Event提交到 EventQueue去了,等到EventDispatchThread線程執(zhí)行完當(dāng)前代碼段的doOtherThing()后,再去執(zhí)行這個 EventQueue中的Event,這時候就會執(zhí)行到這個invokeRemoteService ()方法。但是,實際上這兩個方法都是在EventDispatchThread中執(zhí)行的,并沒有任何其他Thread來執(zhí)行。于是,問題1的問題還是沒解決。實際上直接new Thread().start()方法就可以了,使用SwingUtilities完全是由于誤解造成的濫用。
測試方法,在位置A和位置B都加上下面這行代碼:
- System.out.println(Thread.currentThread().getId() + Thread.currentThread().getName());
返回的結(jié)果都是一樣的:
21AWT-EventQueue-0 21AWT-EventQueue-0
[討論]
一般情況下(除了系統(tǒng)啟動時后臺創(chuàng)建的Daemon線程),系統(tǒng)的所有執(zhí)行功能邏輯和業(yè)務(wù)邏輯的線程都應(yīng)該是從界面操作觸發(fā)的。我們應(yīng)該清楚哪些需要或應(yīng)該放到EventDispatchThread中去執(zhí)行,哪些需要或應(yīng)該創(chuàng)建一個新線程去執(zhí)行,也需要清醒的知道自己當(dāng)前編寫的是屬于什么邏輯。
這個問題我覺得應(yīng)該把代碼分成3層,第一層,UI層,包括UI控件上的Listener邏輯,這是應(yīng)該給EventDispatchThread 去執(zhí)行的,必須簡短高效,快速return;這一層做不完的事情通過new Thread().start()交給下一層去做,我稱之為控制層;然后控制層再去調(diào)用具體的業(yè)務(wù)代碼,即第三層,業(yè)務(wù)層。所有由UI控件觸發(fā)的邏輯都應(yīng)該這么分。
另一個問題是,Swing并不推薦在EventDispatchThread之外修改界面,那么,如果我們在業(yè)務(wù)層需要repaint某個控件,或者updateUI應(yīng)該怎么辦呢,那就可以使用SwingUtilities來處理了,這才是正確使用SwingUtilities的場景,也是設(shè)計這個工具的目的。
原文鏈接:http://seaman.iteye.com/blog/608584
【編輯推薦】





















