在Java中Swing是線程不安全的,是單線程的設計,這樣的造成結果就是:只 能從事件派發線程訪問將要在屏幕上繪制的Swing組件。事件派發線程是調用 paint和update等回調方法的線程,它還是事件監聽器接口中定義的事件處理方 法,例如,ActionListener中的actionPerformed方法在事件派發線程中調用。
Swing是事件驅動的,所以在回調函數中更新可見的GUI是很自然的事情,比 如,有一個按鈕被按下,項目列表需要更新時,則通常在與該按鈕相關聯的事件 監聽器的actionPerformed方法中來實現該列表的更新,從事件派發線程以外的 線程中更新Swing組件是不正常的。
有時需要從事件派發線程以外的線程中更新Swing組件,例如,在 actionPerformed中有很費時的操作,需要很長時間才能返回,按鈕激活後需要 很長時間才能看到更新的列表,按鈕會長時間保持按下的狀態只到 actionPerformed返回,一般說來耗時的操作不應該在事件處理方法中執行,因 為事件處理返回之前,其他事件是不能觸發的,界面類似於卡住的狀況,所以在 獨立的線程上執行比較耗時的操作可能更好,這會立即更新用戶界面和釋放事件 派發線程去派發其他的事件。
SwingUtilities類提供了兩個方法:invokeLate和invoteAndWait,它們都使 事件派發線程上的可運行對象排隊。當可運行對象排在事件派發隊列的隊首時, 就調用其run方法。其效果是允許事件派發線程調用另一個線程中的任意一個代 碼塊。
只有從事件派發線程才能更新組件。
程序示例:更新組件的錯誤方法
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
GetInfoThread t = new GetInfoThread(Test.this);
t.start();
startButton.setEnabled(false);
}
});
class GetInfoThread extends Thread {
Test applet;
public GetInfoThread(Test applet) {
this.applet = applet;
}
public void run() {
while (true) {
try {
Thread.sleep(500);
applet.getProgressBar().setValue(Math.random() * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
錯誤分析:在actionPerformed中,監聽器把按鈕的允許狀態設置為false, 由於是在事件派發線程上調用actionPerformed,所以setEnabled是一個有效的 操作,但是在GetInfoThread中設置進度條是一個危險的做法,因為事件派發線 程以外的線程更新了進度條,所以運行是不正常的。
1、invokeLater使用
class GetInfoThread extends Thread {
Test applet;
Runnable runx;
int value;
public GetInfoThread(final Test applet) {
this.applet = applet;
runx = new Runnable() {
public void run() {
JProgressBar jpb = applet.getProgressBar();
jpb.setValue(value);
}
};
}
public void run() {
while (true) {
try {
Thread.sleep(500);
value = (int) (Math.random() * 100);
System.out.println(value);
SwingUtilities.invokeLater(runx);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、invokeAndWait
與invoikeLater一樣,invokeAndWait也把可運行對象排入事件派發線程的隊 列中,invokeLater在把可運行的對象放入隊列後就返回,而invokeAndWait一直 等待知道已啟動了可運行的run方法才返回。如果一個操作在另外一個操作執行 之前必須從一個組件獲得信息,則invokeAndWait方法是很有用的。class GetInfoThread extends Thread {
Runnable getValue,setValue;
int value,currentValue;
public GetInfoThread(final Test applet){
getValue=new Runnable(){
public void run(){
JProgressBar pb=applet.getProgressBar();
currentValue=pb.getValue();
}
};
setValue=new Runnable(){
public void run(){
JProgressBar pb=applet.getProgressBar();
pb.setValue(value);
}
}
}
public void run(){
while(true){
try{
Thread.currentThead().sleep(500);
value=(int)(Math.random()*100);
try{
SwingUtilities.invokeAndWait(getValue);//直到getValue可運行的 run方法返回後才返回
}catch(Exception ex){
}
if(currentValue!=value){
SwingUtilities.invokeLater(setValue);
}
}
}catch(Exception ex){
}
}
}
invokeLater和invoikeAndWait的一個重要區別:可以從事件派發線程中調用 invokeLater,卻不能從事件派發線程中調用invokeAndWait,從事件派發線程調 用invokeAndWait的問題是:invokeAndWait鎖定調用它的線程,直到可運行對象 從事件派發線程中派發出去並且該可運行的對象的run方法激活,如果從事件派 發線程調用invoikeAndWait,則會發生死鎖的狀況,因為invokeAndWait正在等 待事件派發,但是,由於是從事件派發線程中調用invokeAndWait,所以直到 invokeAndWait返回後事件才能派發。
ex)actionPerformed();返回的時候事件派發線程才能派發線程,而在 actionPerformed中使用invokeAndWait則會導致actionPerformed不能返回。所 以也就無法派發invokeAndWait中的線程。
由於Swing是線程不安全的,所以,從事件派發線程之外的線程訪問Swing組 件是不安全的,SwingUtilities類提供這兩種方法用於執行事件派發線程中的代 碼