Swing API的設計目標是強大
靈活和易用
特別地
我們希望能讓程序員們方便地建立新的Swing組件
不論是從頭開始還是通過擴展我們所提供的一些組件
出於這個目的
我們不要求Swing組件支持多線程訪問
相反
我們向組件發送請求並在單一線程中執行請求
本文討論線程和Swing組件
目的不僅是為了幫助你以線程安全的方式使用Swing API
而且解釋了我們為什麼會選擇現在這樣的線程方案
本文包括以下內容
單線程規則
Swing線程在同一時刻僅能被一個線程所訪問
一般來說
這個線程是事件派發線程(event
dispatching thread)
規則的例外
有些操作保證是線程安全的
事件分發
如果你需要從事件處理(event
handling)或繪制代碼以外的地方訪問UI
那麼你可以使用SwingUtilities類的invokeLater()或invokeAndWait()方法
創建線程
如果你需要創建一個線程——比如用來處理一些耗費大量計算能力或受I/O能力限制的工作——你可以使用一個線程工具類如SwingWorker或Timer
為什麼我們這樣實現Swing
我們將用一些關於Swing的線程安全的背景資料來結束這篇文章
Swing的規則是
一旦Swing組件被具現化(realized)
所有可能影響或依賴於組件狀態的代碼都應該在事件派發線程中執行
這個規則可能聽起來有點嚇人
但對許多簡單的程序來說
你用不著為線程問題操心
在我們深入如何撰寫Swing代碼之前
讓我們先來定義兩個術語
具現化(realized)和事件派發線程(event
dispatching thread)
具現化的意思是組建的paint()方法已經或可能會被調用
一個作為頂級窗口的Swing組件當調用以下方法時將被具現化
setVisible(true)
show()或(可能令你驚奇)pack()
當一個窗口被具現化
它包含的所有組件都被具現化
另一個具現化一個組件的方法是將它放入到一個已經具現化的容器中
稍後你會看到一些對組件具現化的例子
事件派發線程是執行繪制和事件處理的線程
例如
paint()和actionPerformed()方法會自動在事件派發線程中執行
另一個將代碼放到事件派發線程中執行的方法是使用SwingUtilities類的invokeLater()方法
所有可能影響一個已具現化的Swing組件的代碼都必須在事件派發線程中執行
但這個規則有一些例外
有些方法是線程安全的
在Swing API的文檔中
線程安全的方法用以下文字標記
This method is thread safe
although most Swing methods are not
(這個方法是線程安全的
盡管大多數Swing方法都不是
)
一個應用程序的GUI常常可以在主線程中構建和顯示
下面的典型代碼是安全的
只要沒有(Swing或其他)組件被具現化
public class MyApplication
{
public static void main(String[] args)
{
JFrame f = new JFrame(
Labels
); // 在這裡將各組件
// 加入到主框架……
f
pack();
f
show();
// 不要再做任何GUI工作……
}
}
上面所示的代碼全部在
main
線程中運行
對f
pack()的調用使得JFrame以下的組件都被具現化
這意味著
f
show()調用是不安全的且應該在事件派發線程中執行
盡管如此
只要程序還沒有一個看得到的GUI
JFrame或它的裡面的組件就幾乎不可能在f
show()返回前收到一個paint()調用
因為在f
show()調用之後不再有任何GUI代碼
於是所有GUI工作都從主線程轉到了事件派發線程
因此前面所討論的代碼實際上是線程安全的
一個applet的GUI可以在init()方法中構造和顯示
現有的浏覽器都不會在一個applet的init()和start()方法被調用前繪制它
因而
在一個applet的init()方法中構造GUI是安全的
只要你不對applet中的對象調用show()或setVisible(true)方法
要順便一提的是
如果applet中使用了Swing組件
就必須實現為JApplet的子類
並且
組件應該添加到的JApplet內容窗格(content pane)中
而不要直接添加到JApplet
對任何applet
你都不應該在init()或start()方法中執行費時的初始化操作
而應該啟動一個線程來執行費時的任務
下述JComponent方法是安全的
可以從任何線程調用
repaint()
revalidate()
和invalidate()
repaint()和revalidate()方法為事件派發線程對請求排隊
並分別調用paint()和validate()方法
invalidate()方法只在需要確認時標記一個組件和它的所有直接祖先
監聽者列表可以由任何線程修改
調用addListenerTypeListener()和removeListenerTypeListener()方法總是安全的
對監聽者列表的添加/刪除操作不會對進行中的事件派發有任何影響
注意
revalidate()和舊的validate()方法之間的重要區別是
revalidate()會緩存請求並組合成一次validate()調用
這和repaint()緩存並組合繪制請求類似
大多數初始化後的GUI工作自然地發生在事件派發線程
一旦GUI成為可見
大多數程序都是由事件驅動的
如按鈕動作或鼠標點擊
這些總是在事件派發線程中處理的
不過
總有些程序需要在GUI成為可見後執行一些非事件驅動的GUI工作
比如
在成為可用前需要進行長時間初始化操作的程序
這類程序通常應該在初始化期間就顯示出GUI
然後更新或改變GUI
初始化過程不應該在事件派發線程中進行
否則
重繪組件和事件派發會停止
盡管如此
在初始化之後
GUI的更新/改變還是應該在事件派發線程中進行
理由是線程安全
必須響應非AWT事件來更新GUI的程序
例如
想象一個服務器程序從可能運行在其他機器上的程序得到請求
這些請求可能在任何時刻到達
並且會引起在一些可能未知的線程中對服務器的方法調用
這個方法調用怎樣更新GUI呢?在事件派發線程中執行GUI更新代碼
SwingUtilities類提供了兩個方法來幫助你在事件派發線程中執行代碼
invokeLater()
要求在事件派發線程中執行某些代碼
這個方法會立即返回
不會等待代碼執行完畢
invokeAndWait()
行為與invokeLater()類似
除了這個方法會等待代碼執行完畢
一般地
你可以用invokeLater()來代替這個方法
下面是一些使用這幾個API的例子
請同時參閱《The Java Tutorial》中的
BINGO example
尤其是以下幾個類
CardWindow
ControlPane
Player和OverallStatusPane
使用invokeLater()方法
你可以從任何線程調用invokeLater()方法以請求事件派發線程運行特定代碼
你必須把要運行的代碼放到一個Runnable對象的run()方法中
並將此Runnable對象設為invokeLater()的參數
invokeLater()方法會立即返回
不等待事件派發線程執行指定代碼
這是一個使用invokeLater()方法的例子
Runnable doWorkRunnable = new Runnable()
{
public void run()
{
doWork();
}
};
SwingUtilities
invokeLater(doWorkRunnable);
使用invokeAndWait()方法
invokeAndWait()方法和invokeLater()方法很相似
除了invokeAndWait()方法會等事件派發線程執行了指定代碼才返回
在可能的情況下
你應該盡量用invokeLater()來代替invokeAndWait()
如果你真的要使用invokeAndWait()
請確保調用invokeAndWait()的線程不會在調用期間持有任何其他線程可能需要的鎖
這是一個使用invokeAndWait()的例子
void showHelloThereDialog() throws Exception
{
Runnable showModalDialog = new Runnable()
{
public void run()
{
JOptionPane
showMessageDialog( myMainFrame
Hello There
);
}
};
SwingUtilities
invokeAndWait (showModalDialog);
}
類似地
假設一個線程需要對GUI的狀態進行存取
比如文本域的內容
它的代碼可能類似這樣
void printTextField()
throws Exception {
final String[] myStrings = new String[
];
Runnable getTextFieldText = new Runnable() {
public void run() {
myStrings[
] = textField
getText();
myStrings[
] = textField
getText();
}
};
SwingUtilities
invokeAndWait (getTextFieldText);
System
out
println(myStrings[
] +
+ myStrings[
]);}
如果你能避免使用線程
最好這樣做
線程可能難於使用
並使得程序的debug更困難
一般來說
對於嚴格意義下的GUI工作
線程是不必要的
比如對組件屬性的更新
不管怎麼說
有時候線程是必要的
下列情況是使用線程的一些典型情況
執行一項費時的任務而不必將事件派發線程鎖定
例子包括執行大量計算的情況
會導致大量類被裝載的情況(如初始化)
和為網絡或磁盤I/O而阻塞的情況
重復地執行一項操作
通常在兩次操作間間隔一個預定的時間周期
要等待來自客戶的消息
你可以使
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26726.html