swing线程机制&invokeLater&invokeAndWait

Swing是单线程的,所有界面修改必须在事件派发线程(EDT)中执行。为了防止界面卡住,长时间操作应在额外线程中进行,通过`invokeLater`或`invokeAndWait`将更新界面的任务放入EDT。`invokeLater`在事件队列尾部添加任务并立即返回,而`invokeAndWait`则等待任务执行完毕才返回。需要注意的是,不能在EDT中使用`invokeAndWait`,否则可能导致死锁。
摘要由CSDN通过智能技术生成

Swing线程机制

首先swing是单线程的,这个是这篇文章的前提,也是意义所在,当swing界面程序启动的时候,会启动3个进程,

1、主线程
2、系统工具包线程:负责捕获操作系统事件,然后将事件转换成swing的事件,然后发送到事件派发线程EDT
3、事件派发线程(EDT):将事件派发到各个组件,并负责调用绘制方法更新界面

所有的事件,例如键盘,鼠标事件,都会由工具包线程转换成swing事件,然后放到事件队列EventQueue中,而这个EventQueue的派发机制是由EDT来管理的。

所以任何修改组件状态的方法都应该在EDT中执行,包括构造方法。Swing这样的构造原理经常会造成的情况就是,在EDT中执行长时间的事件,使EDT不能及时响应更新界面的事件,就是所说的界面卡住,这种不光是新手就是比较熟练的程序员也会犯的一个错误。所以必须避免在EDT中执行长时间的操作,而避免的方法就是多线程,启动另外的线程来处理冗长的操作,比如操作数据库,读写文件等,在这过程中可能要更新界面来给用户以提示,比如显示一个进度条,过一段事件更新一下界面,但是在EDT以外的线程中更新界面都是无效的,这在前面已经说过,要更新界面就要将对界面的更新操作放到EDT中,但是事件又是在另外的线程中执行的,要解决这个问题就要使用SwingUtilities提供的一个方法了 invokeLater,

 

 

Java代码   收藏代码
  1. public void actionPerformed(ActionEvent e){   
  2.          new Thread(new Runnable(){   
  3.                   //do something    
  4.                   SwingUtilities.invokeLater(new Runnable(){    
  5.                       public void run(){    
  6.                       //update the GUI   
  7.                     }   
  8.                });    
  9.   
  10.       }).start;    
  11.  }  
 

 

这个方法的作用就是将一个更新界面的任务放到EDT中,EDT会在适当的时候进行调用以更新界面。invokeLater负责创建一个含有Runnable的特定事件,并让其在EDT中排队等待调用,当被调用时就会运行Runnable中的run方法进行派发。

 

invokeLater和invokeAndWait的区别

 

与invoikeLater一样,invokeAndWait也把可运行对象排入事件派发线程的队列中,invokeLater在把可运行的对象放入队列后就返回,而invokeAndWait一直等待知道已启动了可运行的run方法才返回。如果一个操作在另外一个操作执行之前必须从一个组件获得信息,则invokeAndWait方法是很有用的。

一般我们用到invokeLater,都是为了执行事件派发线程中的代码,将一段更新UI事件的代码派发到EventQueue中,但是我们可以从事件派发线程中调用invokeLater,却不能从事件派发线程中调用invokeAndWait,下面我们用代码来例证它们的不同:

 

SwingConsole.java文件:

 

Java代码   收藏代码
  1. package swing.utilities;  
  2.   
  3. import javax.swing.*;  
  4.   
  5. public class SwingConsole {  
  6.     public static void run(final JFrame f, final int width, final int height) {  
  7.     SwingUtilities.invokeLater(new Runnable() {  
  8.         public void run() {  
  9.         f.setTitle(f.getClass().getSimpleName());  
  10.         f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  11.         f.setSize(width, height);  
  12.         f.setVisible(true);  
  13.         }  
  14.     });  
  15.     }  
  16. }  
 

 

 

 TestAction.java文件:

 

 

Java代码   收藏代码
  1. import java.awt.BorderLayout;  
  2. import java.awt.FlowLayout;  
  3. import java.awt.event.ActionEvent;  
  4. import java.awt.event.ActionListener;  
  5. import javax.swing.*;  
  6. import swing.utilities.SwingConsole;  
  7.   
  8. public class TestAction extends JFrame {  
  9.     private static final long serialVersionUID = -7462155330900531124L;  
  10.     private JButton jb1 = new JButton("确定");  
  11.     private JTextField txt = new JTextField(10);  
  12.   
  13.     public TestAction() {  
  14.     jb1.addActionListener(new ActionListener() {  
  15.         public void actionPerformed(ActionEvent e) {  
  16.         String name = ((JButton) e.getSource()).getText();  
  17.         txt.setText(name);  
  18.         }  
  19.     });  
  20.     setLayout(null);  
  21.     add(txt);  
  22.     add(jb1);  
  23.     txt.setBounds(5010020030);  
  24.     jb1.setBounds(2701007030);  
  25.     }  
  26.   
  27.     public static void main(String[] args) {  
  28.     SwingUtilities.invokeLater(new Runnable() {  
  29.         public void run() {  
  30.         SwingConsole.run(new TestAction(), 500500);  
  31.         }  
  32.     });  
  33.     }  
  34. }  
 

我们在启动main线程的时候就把整个SwingConsole派发到EventQueue中,而本身SwingConsole已经处在EventQueue中,我们调用invokeLater 没问题,运行正常!

 

再看我们修改之后的:

 

SwingConsole.java文件:

 

Java代码   收藏代码
  1. package swing.utilities;  
  2.   
  3. import java.lang.reflect.InvocationTargetException;  
  4. import javax.swing.*;  
  5.   
  6. public class SwingConsole {  
  7.     public static void run(final JFrame f, final int width, final int height) {  
  8.     try {  
  9.         SwingUtilities.invokeAndWait(new Runnable() {  
  10.         public void run() {  
  11.             f.setTitle(f.getClass().getSimpleName());  
  12.             f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  13.             f.setSize(width, height);  
  14.             f.setVisible(true);  
  15.         }  
  16.         });  
  17.     } catch (InterruptedException e) {  
  18.         throw new RuntimeException(e);  
  19.     } catch (InvocationTargetException e) {  
  20.         throw new RuntimeException(e);  
  21.     }  
  22.     }  
  23. }  
 

TestAction.java文件:

与前面一样

 

运行结果,出现异常:Exception in thread "AWT-EventQueue-0" java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread

在这里,我们把SwingConsole中的invokeLater改成了invokeAndWait,而之前SwingConsole已经处在EventQueue中了。

 

所以我们可以总结出它们的不同:可以从事件派发线程中调用invokeLater,却不能从事件派发线程中调用invokeAndWait,从事件派发线程调用invokeAndWait的问题是:invokeAndWait锁定调用它的线程,直到可运行对象从事件派发线程中派发出去并且该可运行的对象的run方法激活,如果从事件派发线程调用invoikeAndWait,则会发生死锁的状况,因为invokeAndWait正在等待事件派发,但是,由于是从事件派发线程中调用invokeAndWait,所以直到invokeAndWait返回后事件才能派发。

 

在这里我们还可以看到,在main函数里,我们在一开始就把整个操作都置于EventQueue中,这样虽然安全,但是不适合新手,因为新手很容易犯刚才那样的错误,一不小心就用了invokeAndWait而产生死锁。对于swing中的线程,我也还没有找到理想的处理机制,所以暂时就先到此,以后有经验才跟大家分享吧。

=======================================================================

2011年1月5日14:39:28更新

1.比较保险的方式是,一旦swing组件被实现(setVisiable(true)/show()/pack()或者父组件已经被实现),所有改变组件状态的代码或者依赖于组件状态的程序代码,全部需要给EDT执行。

2.JComponent的repaint、revalidate和invalidate等方法在内部已经将更新UI的事件POST进了EDT线程,所以你可以在其他任何线程任何事件调用这几种方法。

 

Java代码   收藏代码
  1. public void revalidate() {  
  2.     if (getParent() == null) {  
  3.         // Note: We don't bother invalidating here as once added  
  4.         // to a valid parent invalidate will be invoked (addImpl  
  5.         // invokes addNotify which will invoke invalidate on the  
  6.         // new Component). Also, if we do add a check to isValid  
  7.         // here it can potentially be called before the constructor  
  8.         // which was causing some people grief.  
  9.         return;  
  10.     }  
  11.     if (SwingUtilities.isEventDispatchThread()) {  
  12.         invalidate();  
  13.         RepaintManager.currentManager(this).addInvalidComponent(this);  
  14.     }  
  15.     else {  
  16.         Runnable callRevalidate = new Runnable() {  
  17.             public void run() {  
  18.                 revalidate();  
  19.             }  
  20.         };  
  21.         SwingUtilities.invokeLater(callRevalidate);  
  22.     }  
  23. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值