Java使用Swing后就会有三个线程
所以一个是程序启动时就产生的线程,执行main()方法,一个是Swing的事件派发线程(EDT),所以只要使用Swing就设计到多线程操作。
Swing如果只是处理简单操作。这几个线程就可以使用,但如果Swing中需要进行长时间操作,只使用这两个线程就会导致程序出错,如下面这个程序
package test;
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.JPanel;
import javax.swing.JTextField;
public class MySwing extends JFrame {
//定义属性
private JFrame fr = new JFrame();
private JPanel jp = new JPanel(); //面板
private JTextField jt = new JTextField(10);
private JButton jb = new JButton("Start");
/**
* 按钮的监听函数
*/
private void JbListener(){
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try{
jt.setText("1.检查数据合法性...");
Thread.sleep(3000);//模仿检测数据合法性
jt.setText("2.正在导入数据...");
Thread.sleep(4000);//模仿导入数据
jt.setText("3.导入成功!");
}catch (InterruptedException e1) {
e1.printStackTrace();
}
}
});
}
/**
* 初始化Swing
*/
public void Demo(){
jp.add(jt);
jp.add(jb);
jp.setLayout(new FlowLayout());
fr.add(jp);
JbListener();
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.setSize(300, 100);
fr.setVisible(true);
}
public static void main(String[] args) {
new MySwing().Demo();
}
}
击按钮,界面卡住,按钮变得不可触发,直到一段时间(7秒)之后界面显示“3.导入成功”。期间并没有显示“1.检查数据合法性”和“2.正在导入数据。
出现上面这种情况的原因在于importBtn事件是由UI线程响应的,Thread.sleep()阻塞了UI线程,UI线程不会执行任何界面刷新的操作。
什么,你说你还不懂?
在引言中提到EDT线程是按顺序触发的,只有前一个任务完成,它才会执行下一个任务,在上面的程序里执行按钮任务时,由于UI界面更新又新添加了3个任务,但这三个任务不会执行,直到按钮任务完成,逻辑图如下:
如上图所示,在按钮事件处理完之后。EDT才会处理队列中的其他操作,对于界面的更改操作最后是连续执行的,但每一步都很快,所以只能看到最后的结果,即,导入更新。
那么问题来了,怎么改呢?这就得将耗时操作放在其他线程中执行,在其他线程中更新界面的话就通过invokeLater和invokeAndWait这两个方法。
SwingUtilities的invokeLater和invokeAndWait方法可以将一个可执行对象(Runnable)实例追加到EDT的可执行队列中。
那么上面的程序这么改就可以了
package test;
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.JPanel;
import javax.swing.JTextField;
public class MySwing extends JFrame {
//定义属性
private JFrame fr = new JFrame();
private JPanel jp = new JPanel(); //面板
private JTextField jt = new JTextField(10);
private JButton jb = new JButton("Start");
/**
* 按钮的监听函数
*/
private void JbListener(){
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//定义匿名内部类,该类实现Runnable接口
new Thread(new Runnable() {
public void run() {
try{
jt.setText("1.检查数据合法性...");
Thread.sleep(3000);//模仿检测数据合法性
jt.setText("2.正在导入数据...");
Thread.sleep(4000);//模仿导入数据
jt.setText("3.导入成功!");
}catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}).start();
}
});
}
/**
* 初始化Swing
*/
public void Demo(){
jp.add(jt);
jp.add(jb);
jp.setLayout(new FlowLayout());
fr.add(jp);
JbListener();
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.setSize(300, 100);
fr.setVisible(true);
}
public static void main(String[] args) {
new MySwing().Demo();
}
}
重新创建一个线程,在这个新线程中执行所有操作,包括延时及界面更新,这样可以显示我们想要的结果。
但是,不能在其他线程中操作EDT线程,会导致界面不稳定
关于如何不稳定,我没遇到过,其实在开始Swing编程中,大部分人都会有这个不良和的编程习惯,即在其他线程中操作EDT线程。最常见的就是在主程序中直接显示界面,是的,只要你没用invokeLater和invokeAndWait这两个方法,你就肯定犯过这个错误,因为从主线程跳到EDT线程显示界面也需要这两个函数。
重新更改后的程序如下:
package test;
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.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class MySwing extends JFrame {
//定义属性
private JFrame fr = new JFrame();
private JPanel jp = new JPanel(); //面板
private JTextField jt = new JTextField(10);
private JButton jb = new JButton("Start");
/**
* 按钮的监听函数
*/
private void JbListener(){
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//定义匿名内部类,该类实现Runnable接口
new Thread(new Runnable() {
public void run() {
try{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jt.setText("1.检查数据合法性...");
}
});
Thread.sleep(3000);//模仿检测数据合法性
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jt.setText("2.正在导入数据...");
}
});
Thread.sleep(4000);//模仿导入数据
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jt.setText("3.导入成功!");
}
});
}catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}).start();
}
});
}
/**
* 初始化Swing
*/
public void Demo(){
jp.add(jt);
jp.add(jb);
jp.setLayout(new FlowLayout());
fr.add(jp);
JbListener();
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.setSize(300, 100);
fr.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MySwing().Demo();
}
});
}
}
关于invokeLater和invokeAndWait这两个方法的区别,invokeLater在把可运行的对象放入队列后就返回,而invokeAndWait一直等待知道已启动了可运行的run方法才返回。
最后用上面学到的知识编写一个计时器程序
单击start按钮开始计时,单击stop按钮停止计时
程序如下
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.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class MySwing2 extends JFrame {
//定义属性
private JFrame frame = new JFrame();
private JPanel panel = new JPanel(); //面板
private JTextField text = new JTextField(10);
private JButton start = new JButton("Start");
private JButton stop = new JButton("stop");
private int count = 0;
private Boolean Flag=false;
private void AppearanceSetting(){
frame.setSize(300, 100);
frame.setLayout(new FlowLayout());
}
private void FrameSetting(){
text.setEditable(false);
panel.add(text);
panel.add(start);
panel.add(stop);
panel.setLayout(new FlowLayout());
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private void Jb1Listener(){
//start.addActionListener(new Start());
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Flag=true;
new Thread(new Runnable(){
@Override
public void run() {
while(true){
if(Flag){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
SwingUtilities.invokeLater(new Runnable(){
public void run() {
// TODO Auto-generated method stub
text.setText(String.valueOf(count));
}
});
}
}
}
}).start();
}
});
}
private void Jb2Listener(){
stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Flag=false;
}
});
}
public void Demo(){
AppearanceSetting();
FrameSetting();
Jb1Listener();
Jb2Listener();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new MySwing2().Demo();
}
});
}
}
这次多线程的文章前前后后拖了一周快写完,对EDT的理解比在写文章之前更清楚了,多写写还是有好处的,下次更新应该是MySQL相关内容。