现在我们要做一个简单的界面。
包括一个进度条、一个输入框、开始和停止按钮。
需要实现的功能是:
当点击开始按钮,则更新进度条,并且在输入框内把完成的百分比输出(这里只做例子,没有真正去做某个工作)。

1 package test; 2 3 import java.awt.FlowLayout; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 7 import javax.swing.JButton; 8 import javax.swing.JFrame; 9 import javax.swing.JProgressBar; 10 import javax.swing.JTextField; 11 12 public class SwingThreadTest1 extends JFrame { 13 private static final long serialVersionUID = 1L; 14 private static final String STR = "Completed : "; 15 private JProgressBar progressBar = new JProgressBar(); 16 private JTextField text = new JTextField(10); 17 private JButton start = new JButton("Start"); 18 private JButton end = new JButton("End"); 19 private boolean flag = false; 20 private int count = 0; 21 22 public SwingThreadTest1() { 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 33 private void go() { 34 while (count < 100) { 35 try { 36 Thread.sleep(100);// 这里比作要完成的某个耗时的工作 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 // 更新进度条和输入框 41 if (flag) { 42 count++; 43 progressBar.setValue(count); 44 text.setText(STR + String.valueOf(count) + "%"); 45 } 46 } 47 } 48 49 private class Start implements ActionListener { 50 public void actionPerformed(ActionEvent e) { 51 flag = true;// 设置开始更新的标志 52 go();// 开始工作 53 System.out.println(Thread.currentThread().getName()); 54 } 55 } 56 57 private class End implements ActionListener { 58 public void actionPerformed(ActionEvent e) { 59 flag = false;// 停止 60 } 61 } 62 63 public static void main(String[] args) { 64 SwingThreadTest1 fg = new SwingThreadTest1(); 65 fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 66 fg.setSize(300, 100); 67 fg.setVisible(true); 68 } 69 }
运行代码发现,
现象1:当点击了开始按钮,画面就卡住了。按钮不能点击,进度条没有被更新,输入框上也没有任何信息。
原因分析:Swing是线程不安全的,是单线程的设计,所以只能从事件派发线程访问将要在屏幕上绘制的Swing组件。ActionListener的actionPerformed方法是在事件派发线程中调用执行的,而点击了开始按钮后,执行了go()方法,在go()里,虽然也去执行了更新组件的方法
progressBar.setValue(count);
text.setText(STR + String.valueOf(count) + "%");
但由于go()方法直到循环结束,它并没有返回,所以更新组件的操作一直没有被执行,这就造成了画面卡住的现象。
go方法也一直在事件派发进程上,和更新UI在一个进程中,在一个进程中就必然是顺序执行的了。
现象2:过了一段时间(go方法里的循环结束了)后,画面又可以操作,并且进度条被更新,输入框也出现了我们想看到的信息。
原因分析:通过在现象1的分析,很容易联想到,当go()方法返回了,则其他的线程(更新组件)可以被派发了,所以画面上的组件被更新了。
为了让画面不会卡住,我们来修改代码,将耗时的工作放在一个线程里去做。

1 package test; 2 3 import java.awt.FlowLayout; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 7 import javax.swing.JButton; 8 import javax.swing.JFrame; 9 import javax.swing.JProgressBar; 10 import javax.swing.JTextField; 11 12 public class SwingThreadTest2 extends JFrame { 13 private static final long serialVersionUID = 1L; 14 private static final String STR = "Completed : "; 15 private JProgressBar progressBar = new JProgressBar(); 16 private JTextField text = new JTextField(10); 17 private JButton start = new JButton("Start"); 18 private JButton end = new JButton("End"); 19 private boolean flag = false; 20 private int count = 0; 21 22 GoThread t = null; 23 24 public SwingThreadTest2() { 25 this.setLayout(new FlowLayout()); 26 add(progressBar); 27 text.setEditable(false); 28 add(text); 29 add(start); 30 add(end); 31 start.addActionListener(new Start()); 32 end.addActionListener(new End()); 33 } 34 35 private void go() { 36 while (count < 100) { 37 try { 38 Thread.sleep(10); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 if (flag) { 43 count++; 44 System.out.println(count); 45 progressBar.setValue(count); 46 System.out.println(Thread.currentThread().getName()); 47 text.setText(STR + String.valueOf(count) + "%"); 48 } 49 } 50 } 51 52 private class Start implements ActionListener { 53 public void actionPerformed(ActionEvent e) { 54 System.out.println(Thread.currentThread().getName()); 55 flag = true; 56 if (t == null) { 57 t = new GoThread(); 58 t.start(); 59 } 60 } 61 } 62 63 // 执行复杂工作,然后更新组件的线程 64 class GoThread extends Thread { 65 public void run() { 66 // do something... 67 go(); 68 } 69 } 70 71 private class End implements ActionListener { 72 public void actionPerformed(ActionEvent e) { 73 flag = false; 74 } 75 } 76 77 public static void main(String[] args) { 78 SwingThreadTest2 fg = new SwingThreadTest2(); 79 fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 80 fg.setSize(300, 100); 81 fg.setVisible(true); 82 } 83 }
我们执行了程序,结果和我们想要的一样,画面不会卡住了。
那这个程序是否没有问题了呢?
我们自定义了一个线程GoThread,在这里我们完成了那些耗时的工作,可以看作是“工作线程”,
而对于组件的更新,我们也放在了“工作线程”里完成了。
在这里,在事件派发线程以外的线程里设置进度条,是一个危险的操作,运行是不正常的。(对于输入框组件的更新是安全的。)
只有从事件派发线程才能更新组件,根据这个原则,我们来修改我们现有代码。

1 package test; 2 3 import java.awt.FlowLayout; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 7 import javax.swing.JButton; 8 import javax.swing.JFrame; 9 import javax.swing.JProgressBar; 10 import javax.swing.JTextField; 11 import javax.swing.SwingUtilities; 12 13 public class SwingThreadTest3 extends JFrame { 14 private static final long serialVersionUID = 1L; 15 private static final String STR = "Completed : "; 16 private JProgressBar progressBar = new JProgressBar(); 17 private JTextField text = new JTextField(10); 18 private JButton start = new JButton("Start"); 19 private JButton end = new JButton("End"); 20 private boolean flag = false; 21 private int count = 0; 22 23 private GoThread t = null; 24 25 private Runnable run = null;// 更新组件的线程 26 27 public SwingThreadTest3() { 28 this.setLayout(new FlowLayout()); 29 add(progressBar); 30 text.setEditable(false); 31 add(text); 32 add(start); 33 add(end); 34 start.addActionListener(new Start()); 35 end.addActionListener(new End()); 36 37 run = new Runnable() {// 实例化更新组件的线程 38 public void run() { 39 System.out.println(Thread.currentThread().getName()); 40 progressBar.setValue(count); 41 text.setText(STR + String.valueOf(count) + "%"); 42 } 43 }; 44 } 45 46 private void go() { 47 while (count < 100) { 48 try { 49 Thread.sleep(10); 50 } catch (InterruptedException e) { 51 e.printStackTrace(); 52 } 53 if (flag) { 54 count++; 55 System.out.println(Thread.currentThread().getName()); 56 SwingUtilities.invokeLater(run);// 将对象排到事件派发线程的队列中 57 } 58 } 59 } 60 61 private class Start implements ActionListener { 62 public void actionPerformed(ActionEvent e) { 63 flag = true; 64 if (t == null) { 65 t = new GoThread(); 66 t.start(); 67 } 68 } 69 } 70 71 class GoThread extends Thread { 72 public void run() { 73 // do something... 74 go(); 75 } 76 } 77 78 private class End implements ActionListener { 79 public void actionPerformed(ActionEvent e) { 80 flag = false; 81 } 82 } 83 84 public static void main(String[] args) { 85 SwingThreadTest3 fg = new SwingThreadTest3(); 86 fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 87 fg.setSize(300, 100); 88 fg.setVisible(true); 89 } 90 }
解释:SwingUtilities.invokeLater()方法使事件派发线程上的可运行对象排队。当可运行对象排在事件派发队列的队首时,就调用其run方法。其效果是允许事件派发线程调用另一个线程中的任意一个代码块。
还有一个方法SwingUtilities.invokeAndWait()方法,它也可以使事件派发线程上的可运行对象排队。
区别:
下面这个是弹出个alert窗口。若用invokeAndWait(),那么打印一段文字将在你点击了OK buton之后才会执行,而如用invokeLater()则,立马后执行输出操作。

1 package test; 2 3 import java.lang.reflect.InvocationTargetException; 4 5 import javax.swing.JOptionPane; 6 import javax.swing.SwingUtilities; 7 8 public class Invoke { 9 public static void main(String[] args) { 10 Runnable showModalDialog = new Runnable() { 11 public void run() { 12 JOptionPane.showMessageDialog(null, "No active shares found on this IP!"); 13 } 14 }; 15 try { 16 SwingUtilities.invokeAndWait(showModalDialog); 17 System.out.println("sssssssssss"); 18 } catch (InterruptedException e) { 19 // TODO Auto-generated catch block 20 e.printStackTrace(); 21 } catch (InvocationTargetException e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 } 25 } 26 }