很多學JAVA程序員都是從Swing開始的
但很多人對AWT GUI線程的機制並沒有太深的了解
或者說一直都只了解線程的概念
而不了解AWT對線程的使用
我發現很多人碰到線程阻塞的問題
就通過調用 SwingUtilities
invokeLater()來解決
其實這是很容易造成誤會的地方
不要以為Swing 是多線程的
實際上Swing 的UI是單線程的
不要以為SwingUtilities
的兩個invoke是多線程
實際上它還是單線程的
不要以為invokeLater的意思是當前線程執行完再執行目標線程
以為invokeAndWait的意思是等待目標線程執行完再執行當前線程
實際上壓根就不是那麼回事
問題代碼
:大意是在按下某個按鈕的時候調用一個遠程服務
JButton button = new JButton()
button
addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
invokeRemoteService()
//可能需要等待
}
})
在swing系統中
有一個頂級的java
awt
Container(可能是一個JFrame或JDialog實例)
負責啟動一個EventDispatchThread線程
單線程
這個線程是負責處理UI事件的
首先
界面Swing控件向EventDispatchThread的EventQueue提交一個event
由 EventDispatchThread負責調度各個event的執行
例如
按下一個JButton的時候
JButton向EventQueue執行 postEvent
提交一個ActionEvent
EventDispatchThread線程根據調度算法執行到該event的時候
會調用 JButton上的processActionEvent
JButton再調用actionPerformed
這過程並沒有執行任何new Thread()
start()代碼
也就是說JButton的ActionListener
actionPerformed()中的代碼完全是在 EventDispatchThread線程內執行的
所以
假如我們在任何ActionListener
MouseListener等對象中編寫耗時的邏輯
那麼整個Swing系統就會出現響應遲鈍的現象
更有甚者
如果在這些Listener中執行線程wait()
以等待另一個線程的鎖定資源或計算結果
那麼實際上就是 EventDispatchThread線程被阻塞
整個系統界面就會處於無響應狀態
一點反應都沒有
以上是誤解
造成的
了解這個過程
就很容易看出上面這段代碼的問題是什麼原因了
解決的方法也倒比較簡單
直接new Thread()
start()
就可以保證EventDispatchThread執行到當前方法的時候快速返回
以便可以去響應來自用戶界面的其他事件
問題代碼
:大意是在按下某個按鈕的時候調用一個遠程服務
同時處理其他事情
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 ()完成之前就能夠得到執行
所以造成了invokeRemoteService ()/doOtherThing()好像是在兩個線程裡執行的假象
實際上invokeLater是把目標代碼打包成一個Event提交到 EventQueue去了
等到EventDispatchThread線程執行完當前代碼段的doOtherThing()後
再去執行這個 EventQueue中的Event
這時候就會執行到這個invokeRemoteService ()方法
但是
實際上這兩個方法都是在EventDispatchThread中執行的
並沒有任何其他Thread來執行
於是
問題
的問題還是沒解決
實際上直接new Thread()
start()方法就可以了
使用SwingUtilities完全是由於誤解造成的濫用
測試方法
在位置A和位置B都加上下面這行代碼
System
out
println(Thread
currentThread()
getId() + Thread
currentThread()
getName())
返回的結果都是一樣的
AWT
EventQueue
AWT
EventQueue
[討論]
一般情況下(除了系統啟動時後台創建的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