Java Swing GUI 事件分發執行緒
一、事件分發執行緒和主執行緒
預設情況下,所有的AWT或者基於Swing的應用程式,都是開始於兩個執行緒的。其中一個就是主執行緒,它處理main方法裡面的程式碼。另外一個執行緒,被稱作“事件分發執行緒”(Event-dispatching thread),它負責處理事件、繪圖、和佈局。所有在主執行緒中編寫的對GUI進行繪製、處理的語句都會在後臺自動放入到事件分發執行緒中等待處理,而除主執行緒之外的其他執行緒則在自己執行緒中進行處理。
而監聽器裡面的處理方法也是放到事件分發執行緒中進行處理(可以通過靜態方法SwingUtilities.isEventDispatchThread()驗證)舉例來說,你在actionPerformed()方法裡面寫的程式碼是自動在“事件分發執行緒”(Event-dispatching thread)裡面被執行的(你不必為此作什麼事情)。而且,這對所有其他的事件處理方法都是成立的。正是由於這個原因,“事件分發執行緒”(Event-dispatching
thread)在Swing和AWT裡面具有極其重要的作用,這個執行緒在維護元件狀態和顯示方面扮演著一個基礎性的角色。
既然,“事件分發執行緒”(Event-dispatching thread)要執行所有的監聽器裡面的方法,處理事件、繪圖、和佈局等,那麼event-handling, painting,以及layout等方法必須要快速執行,就變的相當的重要了。否則的話,就會為了等待一個事件處理,比方說repaint、layout的完成而被堵塞,這樣你的應用程式看起來就僵住不動了。
二、執行緒安全
在Java中Swing是執行緒不安全的,是單執行緒的設計,這樣的造成結果就是:GUI元件的繪製全在事件分發執行緒上處理才是安全的,在其他執行緒上可能會引起執行緒不安全,導致程式崩潰。
Swing為我們提供了三個常用的API:
1.SwingUtilities.invokeAndWait(Runnable runnable)//同步請求,傳送請求的執行緒會一直等到EDT執行完畢自己的請求後,才會繼續執行剩餘程式碼;
2.SwingUtilities.invokeLater(Runnable runnable)//非同步請求,傳送請求的執行緒在請求新增到EDT的eventQUEUE後,才會執行剩餘程式碼,不必等待請求執行完畢;
3.SwingUtilities.isEventDispatchThread()//判斷當前執行緒是否為事件派發執行緒。
invokeLater()和invokeAndWait()方法可以將主執行緒之外的執行緒放入事件分發執行緒的佇列中等待處理。
程式示例:更新元件的錯誤方法
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)
{
}
}
}