通常新手在设计程序的时候习惯在主线程中更新GUI,当计算任务量小的时候问题暴露不明显,但是这始终是一颗埋着的炸弹,总有一天,当你看到程序“卡”住的时候会明白自己设计的是多么糟糕的界面。
如果目前为止你还没遇到过这个问题,那么试着用一下代码运行一下吧:
import java.awt.FlowLayout;
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;
public class SwingTestWithTrouble extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private JButton startButton = new JButton("Start");
private JButton endButton = new JButton("End");
private JProgressBar progressBar = new JProgressBar();
private JTextField textField = new JTextField(10);
private boolean flag = false;
private int count = 0;
public SwingTestWithTrouble() {
setLayout(new FlowLayout());
add(startButton);
add(endButton);
add(textField);
add(progressBar);
textField.setEditable(false);
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
if (flag == false) {
flag = true;
while (count < 100 && flag == true) {
try {
Thread.sleep(100);// 模拟长任务
count++;
progressBar.setValue(count);
textField.setText("Completed : " + count + "%");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
});
endButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
flag = false;
}
});
}
public static void main(String[] args) {
SwingTestWithTrouble swingTest = new SwingTestWithTrouble();
swingTest.setSize(200, 300);
swingTest.setTitle("SwingTest");
swingTest.setVisible(true);
}
}
当你试着运行完以上的代码,看到“卡住”的界面时候,是不是觉得诧异,为什么代码中的GUI的更新部分到最后才得到应用?那么首先我们先得理解,Swing的大部分组件是非线程安全的, 只能用单线程(Swing专门提供了事件派发线程)访问,就是当主线程在干完它所有的活才会来干Swing的线程中要干的事。这就好理解为什么GUI到最后才更新了。
那么是不是只有坐以待毙,当然不是,我们可以在事件发生时用一个新的线程来处理我们的长任务。这里为了规范和安全,用事件派发线程更新组件,用到SwingUtiLities.invokeLater()或SwingUtiLities.invokeAndWait()。
(两者区别:invokeLater是异步方法,在方法还没执行完就返回, 而inovkeAndWait是同步方法,要等方法执行完后再返回.而且在任何线程中调用invokeLater都不会异常,而在事件派发线程中调用inovkeAndWait会抛出异常, 如果在调用线程与invokeLater或inovkeAndWait方法中的线程有顺序关系,可以使用inovkeAndWait,若没有推荐使用invokeLater。因为inovkeAndWait将会带来一个产生死锁的必要条件--等待运行(意思就是调用线程要等到被调线程结束后才能运行)
这里选择SwingUtiLities.invokeLater()。有了思路以后在看一下修改后的代码:
import java.awt.FlowLayout;
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 SwingTest extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private JButton startButton = new JButton("Start");
private JButton endButton = new JButton("End");
private JProgressBar progressBar = new JProgressBar();
private JTextField textField = new JTextField(10);
private boolean flag = false;
private GoThread t = null;
private int count = 0;
public SwingTest() {
setLayout(new FlowLayout());
add(startButton);
add(endButton);
add(textField);
add(progressBar);
textField.setEditable(false);
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
if (flag == false && t == null) {
flag = true;
t = new GoThread();//新开一个线程
t.start();
}
}
});
endButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
flag = false;
}
});
}
private class GoThread extends Thread {
public void run() {
while (count < 100 && flag == true) {
try {
Thread.sleep(100);//模拟长任务
count++;
SwingUtilities.invokeLater(new Runnable() {//事件委派组件更新任务
public void run() {
progressBar.setValue(count);
textField.setText("Completed : " + count + "%");
}
});
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SwingTest swingTest = new SwingTest();
swingTest.setSize(200, 300);
swingTest.setTitle("SwingTest");
swingTest.setVisible(true);
}
}
现在是不是和你想象的一样了呢?