作家
登录

浅析Swing线程模型和EDT

作者: 来源: 2012-06-12 17:33:15 阅读 我要评论

最近我用Swing写一个测试工具,在阅读我要测试的软件的codes的时候,发现他在更新UI的时候大量的用到了SwingUtilities的invokelater方法。我以前做Swing的应用比较少,大学时代为数不多的几次写Swing程序,我记得都是在main方法里面直接创建Frame和更新界面Embarrassed。

以前,我会这么写:

  1. import java.awt.Color;  
  2. import javax.swing.*;  
  3. public class OldSwingDemo {  
  4.   public static void main(String[] argv) {  
  5.     JLabel bulletin = new JLabel("Hello,World!", JLabel.CENTER);  
  6.     JFrame frame = new JFrame("Bulletin");  
  7.     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  8.     frame.getContentPane().add(bulletin);   
  9.     frame.setSize(200150);  
  10.     frame.setVisible(true);  
  11.     bulletin.setForeground(Color.RED);  
  12.   }  

所以我仔细搜了一下相关资料,了解到了Swing的单线程模型和EDT(Event-Dispatch-Thread),才发现我原来的做法是非常危险的,遂总结如下:

Java Swing是一个单线程图形库,里面的绝大多数代码不是线程安全(thread-safe)的,看看Swing各个组件的API,你可以发现绝大多数没有做同步等线程安全的处理,这意味着它并不是在任何地方都能随便调用的(假如你不是在做实验的话),在不同线程里面随便使用这些API去更新界面元素如设置值,更新颜色很可能会出现问题。

虽然Swing的API不是线程安全,但是如果你按照规范写代码(这个规范后面说),Swing框架用了其他方式来保障线程安全,那就是Event Queue和EDT,我们先来看一幅图:

Event_Dispatch_Thread

从上图我们可以形象的看到,在GUI界面上发出的请求事件如窗口移动,刷新,按钮点击,不管是单个的还是并发的,都会被放入事件队列(Event Queue)里面进行排队,然后事件分发线程(Event Dispatch Thread)会将它们一个一个取出,分派到相应的事件处理方法。前面我们之所以说Swing是单线程图形包就是因为处理GUI事件的事件分发线程只有一个,只要你不停止这个GUI程序,EDT就会永不间断去处理请求。

那这种“单线程队列模型”的好处是什么呢?在ITPUB的javagui的《深入浅出Swing事件分发线程》文中总结了两点:

(1)将同步操作转为异步操作

(2)将并行处理转换为串行顺序处理

我觉得还可以补充一点:(3)极大地简化了界面编程。如果是多线程的模型的话,所有事件处理改成异步线程中进行,那么界面元素的的同步访问就要开发人员自己来做处理,想想也很复杂,所以也就难怪目前大多数GUI框架都是采用的是这种单线程的模型。

那我们我们需要注意什么和遵循什么原则呢?

在《JFC Swing Tutorial》中在如何保持“操作GUI代码线程安全”上做了一个很好的总结:

To avoid the possibility of deadlock, you must take extreme care that Swing components and models are modified or queried>

  • import java.awt.Color;  
  • import javax.swing.JFrame;  
  • import javax.swing.JLabel;  
  • import javax.swing.SwingUtilities;  
  • public class NewSwingDemo {  
  •   public static void main(String[] argv) {  
  •     SwingUtilities.invokeLater(new Runnable() {  
  •       @Override 
  •       public void run() {  
  •         constructUI();  
  •             }  
  •     });  
  •   }  
  •   private static void constructUI() {  
  •     JLabel bulletin = new JLabel("Hello,World!", JLabel.CENTER);  
  •     JFrame frame = new JFrame("Bulletin");  
  •     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  •     frame.getContentPane().add(bulletin);  
  •     frame.setSize(200150);  
  •     frame.setVisible(true);  
  •     bulletin.setForeground(Color.RED);  
  •   }  
  • 但是除了线程安全外,还有两点我们需要注意和理解:

    1. 那种特别耗时的任务不应该把它放到EDT中,否则这个应用程序会变得无法响应。因为EDT会忙于执行你的耗时的任务,而无暇顾及其他GUI事件。(没办法啊,那么多活堆在那,EDT一个人挑,做男人难啊,做EDT更难!)
    2. 如果你在其他线程访问和修改GUI组件,那么你必须要使用SwingUtilities. invokeAndWait(), SwingUtilities. invokeLater() 。他们的俩的都有一个相同的作用就是将要执行的任务放入事件队列(Event Queue)中,好让EDT允许事件派发线程调用另一个线程中的任意一个代码块。

    那么invokeLater()和invokeAndWait()的有什么区别呢?

    单纯从字面上来理解public static void invokeLater(Runnable doRun)就是指里面的Runnable运行体会在稍后被调用运行,整个执行是异步的。

    public static void invokeAndWait(Runnable doRun)就是指里面定义的Runnable运行体会调用运行并等待结果返回,是同步的。

    下面用两个例子来展示他们的区别:

    (1)

    1. public class SwingDemoInvokeAndWait {  
    2.     public static void main(String[] argv) throws InterruptedException, InvocationTargetException {  
    3.  
    4.         SwingUtilities.invokeAndWait(new Runnable() {  
    5.  
    6.             @Override 
    7.             public void run() {  
    8.                 constructUI();  
    9.  
    10.             }  
    11.         });  
    12.  
    13.         final Runnable doHelloWorld = new Runnable() {  
    14.             public void run() {  
    15.  
    16.                 System.out.println("Hello World on " + Thread.currentThread());  
    17.  
    18.             }  
    19.         };  
    20.  
    21.         Thread appThread = new Thread() {  
    22.             public void run() {  
    23.                 try {  
    24.                     SwingUtilities.invokeAndWait(doHelloWorld);  
    25.                 } catch (Exception e) {  
    26.                     e.printStackTrace();  
    27.                 }  
    28.                 System.out.println("Finished on " + Thread.currentThread());  
    29.             }  
    30.         };  
    31.         appThread.start();  
    32.  
    33.     }  
    34.  
    35.     private static void constructUI() {  
    36.         JLabel bulletin = new JLabel("Hello,World!", JLabel.CENTER);  
    37.  
    38.         JFrame frame = new JFrame("Bulletin");  
    39.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
    40.         frame.getContentPane().add(bulletin);  
    41.         frame.setSize(200150);  
    42.         frame.setVisible(true);  
    43.         bulletin.setForeground(Color.RED);  
    44.  
    45.     }  

    由于doHelloWorld是在invokeAndWait中被执行的,所以 一定会等待doHelloWorld方法的执行并返回,即”Hello World>

  • import java.awt.Color;  
  • import java.lang.reflect.InvocationTargetException;  
  •  
  • import javax.swing.JFrame;  
  • import javax.swing.JLabel;  
  • import javax.swing.SwingUtilities;  
  •  
  • public class SwingDemoInvokeLater {  
  •     public static void main(String[] argv) throws InterruptedException, InvocationTargetException {  
  •  
  •  
  •         final Runnable doHelloWorld = new Runnable() {  
  •             public void run() {  
  •  
  •                 System.out.println("Hello World on " + Thread.currentThread());  
  •  
  •             }  
  •         };  
  •  
  •         Thread appThread = new Thread() {  
  •             public void run() {  
  •                 try {  
  •                     SwingUtilities.invokeLater(doHelloWorld);  
  •                 } catch (Exception e) {  
  •                     e.printStackTrace();  
  •                 }  
  •                 System.out.println("Finished on " + Thread.currentThread()+",but this might well be displayed before the other message.");  
  •             }  
  •         };  
  •         appThread.start();  
  •  
  •     }  
  •  
  •     private static void constructUI() {  
  •         JLabel bulletin = new JLabel("Hello,World!", JLabel.CENTER);  
  •  
  •         JFrame frame = new JFrame("Bulletin");  
  •         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  •         frame.getContentPane().add(bulletin);  
  •         frame.setSize(200150);  
  •         frame.setVisible(true);  
  •         bulletin.setForeground(Color.RED);  
  •  
  •     }  
  • 由于doHelloWorld是在invokeLater中被执行的,因而“Finished on”有可能出现在其他信息的前面比如”Hello World On”。

    参考资料:

    (1)Swing Threading and The event-dispatch thread

    (2)Section 9.1.  Why are GUIs Single-threaded? - Java Concurrency in Practice

    (3)How to Use Threads - JFC Swing Tutorial, The: A Guide to Constructing GUIs, Second Edition

    (4)深入浅出Swing事件分发线程

    原文链接:http://www.cnblogs.com/chriswang/archive/2009/09/16/swing-single-thread-queue-mode-and-event-dispatch-thread.html

    【编辑推荐】

    1. Swing使用Substance外观包异常问题
    2. Swing多线程编码过程中的误区
    3. 控件位置可以配置的Swing桌面
    4. Swing特效:渐显效果
    5. 简述Java图形用户界面设计(Swing)

      推荐阅读

      Swing使用Substance外观包异常问题

    问题一:今天更新我的Java版QQ,在网上找到了Substance外观包,效果不错,直接用了,可是设置水印问题时就出现问题,网上有现成的例子JFrame.setDefaultLookAndFeelDecorated(true); JDialog.setDefaultLookAndFeel>>>详细阅读


    本文标题:浅析Swing线程模型和EDT

    地址:http://www.17bianji.com/kaifa2/Java/1205.html

    关键词: 探索发现

    乐购科技部分新闻及文章转载自互联网,供读者交流和学习,若有涉及作者版权等问题请及时与我们联系,以便更正、删除或按规定办理。感谢所有提供资讯的网站,欢迎各类媒体与乐购科技进行文章共享合作。

    网友点评
    自媒体专栏

    评论

    热度

    精彩导读
    栏目ID=71的表不存在(操作类型=0)