熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

Swing多線程編碼過程中的誤區

2013-11-23 19:47:13  來源: Java高級技術 
    很多學JAVA程序員都是從Swing開始的但很多人對AWT GUI線程的機制並沒有太深的了解或者說一直都只了解線程的概念而不了解AWT對線程的使用我發現很多人碰到線程阻塞的問題就通過調用 SwingUtilitiesinvokeLater()來解決
   
    其實這是很容易造成誤會的地方
   
    不要以為Swing 是多線程的實際上Swing 的UI是單線程的
   
    不要以為SwingUtilities的兩個invoke是多線程實際上它還是單線程的
   
    不要以為invokeLater的意思是當前線程執行完再執行目標線程以為invokeAndWait的意思是等待目標線程執行完再執行當前線程實際上壓根就不是那麼回事
   
    問題代碼:大意是在按下某個按鈕的時候調用一個遠程服務
   
    JButton button = new JButton()
   
    buttonaddActionListener(new ActionListener(){
   
    @Override
   
    public void actionPerformed(ActionEvent e) {
   
    invokeRemoteService()//可能需要等待
   
    }
   
    })
   
    在swing系統中有一個頂級的javaawtContainer(可能是一個JFrame或JDialog實例)負責啟動一個EventDispatchThread線程單線程這個線程是負責處理UI事件的
   
    首先界面Swing控件向EventDispatchThread的EventQueue提交一個event由 EventDispatchThread負責調度各個event的執行例如按下一個JButton的時候JButton向EventQueue執行 postEvent提交一個ActionEventEventDispatchThread線程根據調度算法執行到該event的時候會調用 JButton上的processActionEventJButton再調用actionPerformed這過程並沒有執行任何new Thread()start()代碼也就是說JButton的ActionListeneractionPerformed()中的代碼完全是在 EventDispatchThread線程內執行的
   
    所以假如我們在任何ActionListenerMouseListener等對象中編寫耗時的邏輯那麼整個Swing系統就會出現響應遲鈍的現象更有甚者如果在這些Listener中執行線程wait()以等待另一個線程的鎖定資源或計算結果那麼實際上就是 EventDispatchThread線程被阻塞整個系統界面就會處於無響應狀態一點反應都沒有
   
    以上是誤解造成的了解這個過程就很容易看出上面這段代碼的問題是什麼原因了解決的方法也倒比較簡單直接new Thread()start()就可以保證EventDispatchThread執行到當前方法的時候快速返回以便可以去響應來自用戶界面的其他事件
   
    問題代碼:大意是在按下某個按鈕的時候調用一個遠程服務同時處理其他事情
   
    JButton button = new JButton()
   
    buttonaddActionListener(new ActionListener(){
   
    @Override
   
    public void actionPerformed(ActionEvent e) {
   
    //位置A
   
    SwingUtilitiesinvokeLater(new Runnable() {
   
         public void run() {
   
    //位置B
   
             invokeRemoteService()//可能需要等待
   
         }
   
    })
   
    doOtherThing()
   
    }
   
    })
   
    這段代碼跟第一段代碼唯一的差別是doOtherThing()在invokeRemoteService ()完成之前就能夠得到執行所以造成了invokeRemoteService ()/doOtherThing()好像是在兩個線程裡執行的假象實際上invokeLater是把目標代碼打包成一個Event提交到 EventQueue去了等到EventDispatchThread線程執行完當前代碼段的doOtherThing()後再去執行這個 EventQueue中的Event這時候就會執行到這個invokeRemoteService ()方法但是實際上這兩個方法都是在EventDispatchThread中執行的並沒有任何其他Thread來執行於是問題的問題還是沒解決實際上直接new Thread()start()方法就可以了使用SwingUtilities完全是由於誤解造成的濫用
   
    測試方法在位置A和位置B都加上下面這行代碼
   
    Systemoutprintln(ThreadcurrentThread()getId() + ThreadcurrentThread()getName())
   
    返回的結果都是一樣的
   
    AWTEventQueue
   
    AWTEventQueue
   
    [討論]
   
    一般情況下(除了系統啟動時後台創建的Daemon線程)系統的所有執行功能邏輯和業務邏輯的線程都應該是從界面操作觸發的我們應該清楚哪些需要或應該放到EventDispatchThread中去執行哪些需要或應該創建一個新線程去執行也需要清醒的知道自己當前編寫的是屬於什麼邏輯
   
    這個問題我覺得應該把代碼分成第一層UI層包括UI控件上的Listener邏輯這是應該給EventDispatchThread 去執行的必須簡短高效快速return;這一層做不完的事情通過new Thread()start()交給下一層去做我稱之為控制層然後控制層再去調用具體的業務代碼即第三層業務層所有由UI控件觸發的邏輯都應該這麼分
   
    另一個問題是Swing並不推薦在EventDispatchThread之外修改界面那麼如果我們在業務層需要repaint某個控件或者updateUI應該怎麼辦呢那就可以使用SwingUtilities來處理了這才是正確使用SwingUtilities的場景也是設計這個工具的目的
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27454.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.