java Swing 是一個單線程圖形庫,里面絕大多數的代碼不是線程安全的(thread-safe),Swing組件大多數沒有做同步線程安全處理,也就是說任何地方都能隨便調用,在不同的線程里面隨便使用這些API去更新界面元素和設置值,都可能會出現一些問題。 Swing框架使用了Event Queue和EDT(Event-Dispatch-Thread)來保證線程是安全的。在GUI界面上發出的請求事件如窗口移動,刷新,按鈕點擊等不管是單個的還是并發的,都會背包放入事件隊列(Event Queue )里面進行排隊,然后事件分發線程(EDT)將會將它們一個個的取出,分派到相應的事件處理方法中。 Swing是單線程圖形包就是因為處理GUI事件的事件分發線程只有一個,只要不停之GUI程序,EDT就會永不間斷去處理請求。其優點是:1、將同步操作轉化為異步操作;2、將并行處理轉換為串行處理。 Swing編程時應該注意以下三點: 1.從其他線程訪問UI組件及其事件處理器可能會導致界面更新和繪制錯誤。 2.在EDT上執行耗時任務會使程序失去響應,這會使GUI事件阻塞在隊列中得不到處理 3.應該使用獨立的線程任務來執行耗時計算或輸入輸出密集型任務,比如同數據庫通信、訪問網站資源、讀寫大數據量的文件
許多程序使用下面方法啟動界面,但這是錯誤的啟動UI界面的方法:
public class MainFrameextends javax.swing.JFrame { … public static void main(String[] args){ new MainFrame().setVisible(true); }}盡管這種錯誤出現在開始,但仍然違反了不應在EDT外的其他線程同Swing組件交互的原則。這個錯誤尤其容易犯,線程同步問題雖然不是馬上顯示出來,但是還要注意避免這樣書寫。
正確啟動UI界面應該如下:
public class MainFrame extends javax.swing.JFrame{ … public static void main(String[] args){ SwingUtilities.invokeLater(new Runnable(){ public void run(){ newMainFrame().setVisible(true); } }); }}當運行一個Swing程序時,會自動創建三個線程。 1.主線程,負責執行main 方法 2. toolkit 線程,負責捕捉系統事件,比如鍵盤、鼠標移動等,程序員不會有任何代碼在這個線程上執行。Toolkit線程的作用是把自己捕獲的事件傳遞給第三個線程,也就是事件派發線程。 3. 事件派發線程(EDT,Event Dispatcher Thread),顧名思義是用來派發事件(根據事件找到對應的事件處理代碼)的線程。EDT接收來自 toolkit 線程的事件,并且將這些事件組織成一個隊列,EDT的工作內容就是將這個隊列中的事件按照順序派發給相應的事件監聽器,并且調用事件監聽器中的回調函數,這也意味著,所有的事件處理代碼都是在EDT而不是主線程中執行。 4. 上面說到EDT中維護了一個事件的隊列,并且它們是按照順序派發的。由于事件派發是單線程的操作,所以只有等待前面事件監聽器的回調函數執行完畢,才能夠執行組件更新的操作,以及繼續派發后面的事件。這樣導致的一個后果就是:當在一個事件監聽回調函數中做了耗時的操作,那么,界面會因此停住,并且界面上所有控件失效(不可觸發)。 解決這個問題的方法是:在事件處理函數中將耗時的操作放到新線程(一般稱之為工作線程)中執行,而不是讓其在EDT中執行。 一個窗口,有一個按鈕和一個label。點擊按鈕,系統將做模仿導入數據的動作,導入數據之前需要檢測數據的合法性。并且,檢測數據和導入數據這兩個步驟都需要耗費一定的時間。 如果沒有之前說到的EDT的概念,那么你可能會這么做:
importBtn.addActionListener(newActionListener() { @Override publicvoid actionPerformed(ActionEvent e) { try{ lb.setText("1.檢查數據合法性..."); Thread.sleep(3000);//模仿檢測數據合法性 lb.setText("2.正在導入數據..."); Thread.sleep(4000);//模仿導入數據 lb.setText("3.導入成功!"); }catch (InterruptedException e1) { e1.PRintStackTrace(); } } });但是,如果運行一下的話,會發現現象是這樣:點擊按鈕,界面卡住,按鈕變得不可觸發,直到一段時間(7秒)之后界面顯示“3.導入成功”。期間并沒有顯示“1.檢查數據合法性”和“2.正在導入數據”。 這個現象印證了上面說的理論:當事件派發線程中正在執行的事件監聽函數執行完畢,才能進行UI組件的刷新操作,并且派發事件隊列中的下一個。
package test;import java.awt.FlowLayout;import java.awt.HeadlessException;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JProgressBar;import javax.swing.JTextField;import javax.swing.SwingUtilities;public class SwingThreadTest3 extends JFrame { /** * */ private static final long serialVersionUID = -6785759504284102780L; private JProgressBar progressBar = new JProgressBar(); //進度條 private JTextField text = new JTextField(10); private JButton start = new JButton("Start"); private JButton end = new JButton("End"); private boolean flag = false; private int count =0; private GoThread t = null; //繼續線程 private Runnable run = null; //更新組件線程 public SwingThreadTest3() { this.setLayout(new FlowLayout()); add(progressBar); text.setEditable(flag); add(text); add(start); add(end); start.addActionListener(new Start()); end.addActionListener(new End()); run = new Runnable() { public void run() { progressBar.setValue(count); text.setText("Completed : " + String.valueOf(count) + "%"); } }; } class GoThread extends Thread { @Override public void run() { while (count < 100) { try { Thread.sleep(100); //線程沉睡 } catch (Exception e) { e.printStackTrace(); } if (flag) { count++; SwingUtilities.invokeLater(run); //將對象排到事件派發線程的隊列中 } } } } private class Start implements ActionListener { public void actionPerformed(ActionEvent e) { flag = true; if(t == null) { t = new GoThread(); t.start(); } } } private class End implements ActionListener { public void actionPerformed(ActionEvent e) { flag = false; } } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { SwingThreadTest3 s3 = new SwingThreadTest3(); s3.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE); s3.setSize(300, 300); s3.setVisible(true); } }); }}參考資料:http://983836259.blog.51cto.com/7311475/1724198 http://developer.51cto.com/art/201201/313034.htm
新聞熱點
疑難解答