新聞中心
很多學(xué)JAVA程序員都是從Swing開始的,但很多人對AWT GUI線程的機(jī)制并沒有太深的了解,或者說一直都只了解線程的概念,而不了解AWT對線程的使用。我發(fā)現(xiàn)很多人碰到線程阻塞的問題,就通過調(diào)用 SwingUtilities.invokeLater()來解決。

目前成都創(chuàng)新互聯(lián)公司已為成百上千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁空間、網(wǎng)站托管運(yùn)營、企業(yè)網(wǎng)站設(shè)計(jì)、徐匯網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
其實(shí)這是很容易造成誤會(huì)的地方:
- 不要以為Swing 是多線程的,實(shí)際上Swing 的UI是單線程的
- 不要以為SwingUtilities.的兩個(gè)invoke是多線程,實(shí)際上它還是單線程的
- 不要以為invokeLater的意思是當(dāng)前線程執(zhí)行完再執(zhí)行目標(biāo)線程;以為invokeAndWait的意思是等待目標(biāo)線程執(zhí)行完再執(zhí)行當(dāng)前線程,實(shí)際上壓根就不是那么回事
問題代碼1:大意是在按下某個(gè)按鈕的時(shí)候調(diào)用一個(gè)遠(yuǎn)程服務(wù)
- JButton button = new JButton();
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- invokeRemoteService();//可能需要等待
- }
- });
在swing系統(tǒng)中,有一個(gè)頂級(jí)的java.awt.Container(可能是一個(gè)JFrame或JDialog實(shí)例),負(fù)責(zé)啟動(dòng)一個(gè)EventDispatchThread線程,單線程,這個(gè)線程是負(fù)責(zé)處理UI事件的。
首先,界面Swing控件向EventDispatchThread的EventQueue提交一個(gè)event,由 EventDispatchThread負(fù)責(zé)調(diào)度各個(gè)event的執(zhí)行。例如,按下一個(gè)JButton的時(shí)候,JButton向EventQueue執(zhí)行 postEvent,提交一個(gè)ActionEvent。EventDispatchThread線程根據(jù)調(diào)度算法執(zhí)行到該event的時(shí)候,會(huì)調(diào)用 JButton上的processActionEvent,JButton再調(diào)用actionPerformed,這過程并沒有執(zhí)行任何new Thread().start()代碼,也就是說JButton的ActionListener.actionPerformed()中的代碼完全是在 EventDispatchThread線程內(nèi)執(zhí)行的。
所以,假如我們在任何ActionListener、MouseListener等對象中編寫耗時(shí)的邏輯,那么整個(gè)Swing系統(tǒng)就會(huì)出現(xiàn)響應(yīng)遲鈍的現(xiàn)象,更有甚者,如果在這些Listener中執(zhí)行線程wait(),以等待另一個(gè)線程的鎖定資源或計(jì)算結(jié)果,那么實(shí)際上就是 EventDispatchThread線程被阻塞,整個(gè)系統(tǒng)界面就會(huì)處于無響應(yīng)狀態(tài),一點(diǎn)反應(yīng)都沒有。
以上是誤解1造成的,了解這個(gè)過程,就很容易看出上面這段代碼的問題是什么原因了。解決的方法也倒比較簡單,直接new Thread().start();就可以保證EventDispatchThread執(zhí)行到當(dāng)前方法的時(shí)候快速返回,以便可以去響應(yīng)來自用戶界面的其他事件。
問題代碼2:大意是在按下某個(gè)按鈕的時(shí)候調(diào)用一個(gè)遠(yuǎn)程服務(wù),同時(shí)處理其他事情
- 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()好像是在兩個(gè)線程里執(zhí)行的假象。實(shí)際上invokeLater是把目標(biāo)代碼打包成一個(gè)Event提交到 EventQueue去了,等到EventDispatchThread線程執(zhí)行完當(dāng)前代碼段的doOtherThing()后,再去執(zhí)行這個(gè) EventQueue中的Event,這時(shí)候就會(huì)執(zhí)行到這個(gè)invokeRemoteService ()方法。但是,實(shí)際上這兩個(gè)方法都是在EventDispatchThread中執(zhí)行的,并沒有任何其他Thread來執(zhí)行。于是,問題1的問題還是沒解決。實(shí)際上直接new Thread().start()方法就可以了,使用SwingUtilities完全是由于誤解造成的濫用。
測試方法,在位置A和位置B都加上下面這行代碼:
- System.out.println(Thread.currentThread().getId() + Thread.currentThread().getName());
返回的結(jié)果都是一樣的:
21AWT-EventQueue-0 21AWT-EventQueue-0
[討論]
一般情況下(除了系統(tǒng)啟動(dòng)時(shí)后臺(tái)創(chuàng)建的Daemon線程),系統(tǒng)的所有執(zhí)行功能邏輯和業(yè)務(wù)邏輯的線程都應(yīng)該是從界面操作觸發(fā)的。我們應(yīng)該清楚哪些需要或應(yīng)該放到EventDispatchThread中去執(zhí)行,哪些需要或應(yīng)該創(chuàng)建一個(gè)新線程去執(zhí)行,也需要清醒的知道自己當(dāng)前編寫的是屬于什么邏輯。
這個(gè)問題我覺得應(yīng)該把代碼分成3層,第一層,UI層,包括UI控件上的Listener邏輯,這是應(yīng)該給EventDispatchThread 去執(zhí)行的,必須簡短高效,快速return;這一層做不完的事情通過new Thread().start()交給下一層去做,我稱之為控制層;然后控制層再去調(diào)用具體的業(yè)務(wù)代碼,即第三層,業(yè)務(wù)層。所有由UI控件觸發(fā)的邏輯都應(yīng)該這么分。
另一個(gè)問題是,Swing并不推薦在EventDispatchThread之外修改界面,那么,如果我們在業(yè)務(wù)層需要repaint某個(gè)控件,或者updateUI應(yīng)該怎么辦呢,那就可以使用SwingUtilities來處理了,這才是正確使用SwingUtilities的場景,也是設(shè)計(jì)這個(gè)工具的目的。
原文鏈接:http://seaman.iteye.com/blog/608584
【編輯推薦】
- 控件位置可以配置的Swing桌面
- Swing特效:漸顯效果
- 簡述Java圖形用戶界面設(shè)計(jì)(Swing)
- 用Swing制作精美的圖層疊加圖
- 簡述Java圖形用戶界面設(shè)計(jì)(Swing)
新聞標(biāo)題:Swing多線程編碼過程中的誤區(qū)
網(wǎng)頁地址:http://fisionsoft.com.cn/article/dpoopih.html


咨詢
建站咨詢
