1. 程式人生 > >java解決swing單執行緒卡死

java解決swing單執行緒卡死

現在我們要做一個簡單的介面。
包括一個進度條、一個輸入框、開始和停止按鈕。
需要實現的功能是:
當點選開始按鈕,則更新進度條,並且在輸入框內把完成的百分比輸出(這裡只做例子,沒有真正去做某個工作)。


程式碼1:
view plaincopy to clipboardprint?
1. import java.awt.FlowLayout;  
2. import java.awt.event.ActionEvent;  
3. import java.awt.event.ActionListener;  
4. import javax.swing.JButton;  
5. import javax.swing.JFrame;  
6. import javax.swing.JProgressBar;  
7. import javax.swing.JTextField;  
8. public class SwingThreadTest1 extends JFrame {  
9.        private static final long serialVersionUID = 1L;  
10.        private static final String STR = "Completed : ";  
11.        private JProgressBar progressBar = new JProgressBar();  
12.        private JTextField text = new JTextField(10);  
13.        private JButton start = new JButton("Start");  
14.        private JButton end = new JButton("End");  
15.        private boolean flag = false;  
16.        private int count = 0;  
17.        public SwingThreadTest1() {  
18.          this.setLayout(new FlowLayout());  
19.          add(progressBar);  
20.          text.setEditable(false);  
21.          add(text);  
22.          add(start);  
23.          add(end);  
24.          start.addActionListener(new Start());  
25.          end.addActionListener(new End());  
26.        }  
27.             
28.        private void go() {  
29.          while (count < 100) {  
30.             try {  
31.                    Thread.sleep(100);//這裡比作要完成的某個耗時的工作  
32.             } catch (InterruptedException e) {  
33.                    e.printStackTrace();  
34.             }  
35.                             //更新進度條和輸入框  
36.             if (flag) {  
37.                    count++;  
38.                    progressBar.setValue(count);  
39.                    text.setText(STR + String.valueOf(count) + "%");  
40.             }  
41.          }  
42.        }  
43.        private class Start implements ActionListener {  
44.          public void actionPerformed(ActionEvent e) {  
45.             flag = true;//設定開始更新的標誌  
46.             go();//開始工作  
47.          }  
48.        }  
49.        private class End implements ActionListener {  
50.          public void actionPerformed(ActionEvent e) {  
51.             flag = false;//停止  
52.          }  
53.        }  
54.        public static void main(String[] args) {  
55.          SwingThreadTest1 fg = new SwingThreadTest1();  
56.          fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
57.          fg.setSize(300, 100);  
58.          fg.setVisible(true);  
59.        }  
60. }  


執行程式碼發現,
現象1:當點選了開始按鈕,畫面就卡住了。按鈕不能點選,進度條沒有被更新,輸入框上也沒有任何資訊。
原因分析:Swing是執行緒不安全的,是單執行緒的設計,所以只能從事件派發執行緒訪問將要在螢幕上繪製的Swing元件。ActionListener的actionPerformed方法是在事件派發執行緒中呼叫執行的,而點選了開始按鈕後,執行了go()方法,在go()裡,雖然也去執行了更新元件的方法
progressBar.setValue(count);
text.setText(STR + String.valueOf(count) + "%");
但由於go()方法直到迴圈結束,它並沒有返回,所以更新元件的操作一直沒有被執行,這就造成了畫面卡住的現象。
現象2:過了一段時間(go方法裡的迴圈結束了)後,畫面又可以操作,並且進度條被更新,輸入框也出現了我們想看到的資訊。
原因分析:通過在現象1的分析,很容易聯想到,當go()方法返回了,則其他的執行緒(更新元件)可以被派發了,所以畫面上的元件被更新了。
為了讓畫面不會卡住,我們來修改程式碼,將耗時的工作放在一個執行緒裡去做。


程式碼2:
view plaincopy to clipboardprint?
1. import java.awt.FlowLayout;  
2. import java.awt.event.ActionEvent;  
3. import java.awt.event.ActionListener;  
4. import javax.swing.JButton;  
5. import javax.swing.JFrame;  
6. import javax.swing.JProgressBar;  
7. import javax.swing.JTextField;  
8. public class SwingThreadTest2 extends JFrame {  
9.        private static final long serialVersionUID = 1L;  
10.        private static final String STR = "Completed : ";  
11.        private JProgressBar progressBar = new JProgressBar();  
12.        private JTextField text = new JTextField(10);  
13.        private JButton start = new JButton("Start");  
14.        private JButton end = new JButton("End");  
15.        private boolean flag = false;  
16.        private int count = 0;  
17.       
18.        GoThread t = null;  
19.        public SwingThreadTest2() {  
20.          this.setLayout(new FlowLayout());  
21.          add(progressBar);  
22.          text.setEditable(false);  
23.          add(text);  
24.          add(start);  
25.          add(end);  
26.          start.addActionListener(new Start());  
27.          end.addActionListener(new End());  
28.        }  
29.        private void go() {  
30.          while (count < 100) {  
31.             try {  
32.                    Thread.sleep(100);  
33.             } catch (InterruptedException e) {  
34.                    e.printStackTrace();  
35.             }  
36.             if (flag) {  
37.                    count++;  
38.                    System.out.println(count);  
39.                    progressBar.setValue(count);  
40.                    text.setText(STR + String.valueOf(count) + "%");  
41.             }  
42.          }  
43.        }  
44.        private class Start implements ActionListener {  
45.          public void actionPerformed(ActionEvent e) {  
46.             flag = true;  
47.             if(t == null){  
48.                    t = new GoThread();  
49.                    t.start();  
50.             }  
51.          }  
52.        }  
53.        //執行復雜工作,然後更新元件的執行緒  
54.        class GoThread extends Thread{  
55.          public void run() {  
56.             //do something...  
57.             go();  
58.          }  
59.        }  
60.        private class End implements ActionListener {  
61.          public void actionPerformed(ActionEvent e) {  
62.             flag = false;  
63.          }  
64.        }  
65.        public static void main(String[] args) {  
66.          SwingThreadTest2 fg = new SwingThreadTest2();  
67.          fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
68.          fg.setSize(300, 100);  
69.          fg.setVisible(true);  
70.        }  
71. }  


我們執行了程式,結果和我們想要的一樣,畫面不會卡住了。
那這個程式是否沒有問題了呢?
我們自定義了一個執行緒GoThread,在這裡我們完成了那些耗時的工作,可以看作是“工作執行緒”,
而對於元件的更新,我們也放在了“工作執行緒”裡完成了。
在這裡,在事件派發執行緒以外的執行緒裡設定進度條,是一個危險的操作,執行是不正常的。(對於輸入框元件的更新是安全的。)


只有從事件派發執行緒才能更新元件,根據這個原則,我們來修改我們現有程式碼。
程式碼3:
view plaincopy to clipboardprint?
1. import java.awt.FlowLayout;  
2. import java.awt.event.ActionEvent;  
3. import java.awt.event.ActionListener;  
4. import javax.swing.JButton;  
5. import javax.swing.JFrame;  
6. import javax.swing.JProgressBar;  
7. import javax.swing.JTextField;  
8. import javax.swing.SwingUtilities;  
9. public class SwingThreadTest3 extends JFrame {  
10.        private static final long serialVersionUID = 1L;  
11.        private static final String STR = "Completed : ";  
12.        private JProgressBar progressBar = new JProgressBar();  
13.        private JTextField text = new JTextField(10);  
14.        private JButton start = new JButton("Start");  
15.        private JButton end = new JButton("End");  
16.        private boolean flag = false;  
17.        private int count = 0;  
18.       
19.        private GoThread t = null;  
20.       
21.        private Runnable run = null;//更新元件的執行緒  
22.        public SwingThreadTest3() {  
23.          this.setLayout(new FlowLayout());  
24.          add(progressBar);  
25.          text.setEditable(false);  
26.          add(text);  
27.          add(start);  
28.          add(end);  
29.          start.addActionListener(new Start());  
30.          end.addActionListener(new End());  
31.             
32.          run = new Runnable(){//例項化更新元件的執行緒  
33.             public void run() {  
34.                    progressBar.setValue(count);  
35.                    text.setText(STR + String.valueOf(count) + "%");  
36.             }  
37.          };  
38.        }  
39.        private void go() {  
40.          while (count < 100) {  
41.             try {  
42.                    Thread.sleep(100);  
43.             } catch (InterruptedException e) {  
44.                    e.printStackTrace();  
45.             }  
46.             if (flag) {  
47.                    count++;  
48.                    SwingUtilities.invokeLater(run);//將物件排到事件派發執行緒的佇列中  
49.             }  
50.          }  
51.        }  
52.        private class Start implements ActionListener {  
53.          public void actionPerformed(ActionEvent e) {  
54.             flag = true;  
55.             if(t == null){  
56.                    t = new GoThread();  
57.                    t.start();  
58.             }  
59.          }  
60.        }  
61.       
62.        class GoThread extends Thread{  
63.          public void run() {  
64.             //do something...  
65.             go();  
66.          }  
67.        }  
68.        private class End implements ActionListener {  
69.          public void actionPerformed(ActionEvent e) {  
70.             flag = false;  
71.          }  
72.        }  
73.        public static void main(String[] args) {  
74.          SwingThreadTest3 fg = new SwingThreadTest3();  
75.          fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
76.          fg.setSize(300, 100);  
77.          fg.setVisible(true);  
78.        }  
79. }  


解釋:SwingUtilities.invokeLater()方法使事件派發執行緒上的可執行物件排隊。當可執行物件排在事件派發佇列的隊首時,就呼叫其run方法。其效果是允許事件派發執行緒呼叫另一個執行緒中的任意一個程式碼塊。
還有一個方法SwingUtilities.invokeAndWait()方法,它也可以使事件派發執行緒上的可執行物件排隊。
他們的不同之處在於:SwingUtilities.invokeLater()在把可執行的物件放入佇列後就返回,而SwingUtilities.invokeAndWait()一直等待知道已啟動了可執行的run方法才返回。如果一個操作在另外一個操作執行之前必須從一個元件獲得資訊,則應使用SwingUtilities.invokeAndWait()方法