Swing线程机制以及invokeLater和invokeAndWait

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.                    pulic void run(){ 

6.                        //update the GUI 

7.                    }     

8.            }); 

9.        }).start; 

10.   }


这个方法的作用就是将一个更新界面的任务放到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.    import javax.swing.*; 

3.    public class SwingConsole { 

4.        public static void run(final JFrame f, final int width, final int height){ 

5.        SwingUtilities.invokeLater(new Runnable(){ 

6.            public void run(){ 

7.            f.setTitle(f.getClass().getSimpleName()); 

8.            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

9.            f.setSize(width, height); 

10.           f.setVisible(true); 

11.           } 

12.       }); 

13.       } 

14.   }

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(50, 100, 200, 30); 

24.       jb1.setBounds(270, 100, 70, 30); 

25.       } 

26.   

27.       public static void main(String[] args) { 

28.       SwingUtilities.invokeLater(new Runnable() { 

29.           public void run() { 

30.           SwingConsole.run(new TestAction(), 500, 500); 

31.           } 

32.       }); 

33.   

34.       } 

35.   

36.   }

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


再看我们修改之后的:


SwingConsole.java文件:

Java代码

1.    package swing.utilities; 

2.    import java.lang.reflect.InvocationTargetException; 

3.    

4.    import javax.swing.*; 

5.    public class SwingConsole { 

6.        public static void run(final JFrame f, final int width, final int height){ 

7.        try { 

8.            SwingUtilities.invokeAndWait(new Runnable(){ 

9.                public void run(){ 

10.               f.setTitle(f.getClass().getSimpleName()); 

11.               f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

12.               f.setSize(width, height); 

13.               f.setVisible(true); 

14.               } 

15.           }); 

16.       } catch (InterruptedException e) { 

17.           throw new RuntimeException(e); 

18.       } catch (InvocationTargetException e) { 

19.           throw new RuntimeException(e); 

20.       } 

21.       } 

22.   }

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中的线程,我也还没有找到理想的处理机制,所以暂时就先到此,以后有经验才跟大家分享吧。

你可能感兴趣的:(thread,多线程,UI,swing,F#)