最近我用Swing写一个测试工具,在阅读我要测试的软件的codes的时候,发现他在更新UI的时候大量的用到了SwingUtilities的invokelater方法。我以前做Swing的应用比较少,大学时代为数不多的几次写Swing程序,我记得都是在main方法里面直接创建Frame和更新界面Embarrassed。
创新互联建站成立与2013年,先为隆子等服务建站,隆子等地企业,进行企业商务咨询服务。为隆子企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。
以前,我会这么写:
- import java.awt.Color;
- import javax.swing.*;
- public class OldSwingDemo {
- public static void main(String[] argv) {
- 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(200, 150);
- frame.setVisible(true);
- bulletin.setForeground(Color.RED);
- }
- }
所以我仔细搜了一下相关资料,了解到了Swing的单线程模型和EDT(Event-Dispatch-Thread),才发现我原来的做法是非常危险的,遂总结如下:
Java Swing是一个单线程图形库,里面的绝大多数代码不是线程安全(thread-safe)的,看看Swing各个组件的API,你可以发现绝大多数没有做同步等线程安全的处理,这意味着它并不是在任何地方都能随便调用的(假如你不是在做实验的话),在不同线程里面随便使用这些API去更新界面元素如设置值,更新颜色很可能会出现问题。
虽然Swing的API不是线程安全,但是如果你按照规范写代码(这个规范后面说),Swing框架用了其他方式来保障线程安全,那就是Event Queue和EDT,我们先来看一幅图:
从上图我们可以形象的看到,在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 only from the event-dispatching thread. As long as your program creates its GUI from the event-dispatching thread and modifies the GUI only from event handlers, it is thread safe.
只要你是在EDT中创建GUI,在事件处理器中修改GUI的,那么你的代码在Swing这块就是线程安全的。
所以前面的代码应该修改成这样:
- 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(200, 150);
- frame.setVisible(true);
- bulletin.setForeground(Color.RED);
- }
- }
但是除了线程安全外,还有两点我们需要注意和理解:
那么invokeLater()和invokeAndWait()的有什么区别呢?
单纯从字面上来理解public static void invokeLater(Runnable doRun)就是指里面的Runnable运行体会在稍后被调用运行,整个执行是异步的。
public static void invokeAndWait(Runnable doRun)就是指里面定义的Runnable运行体会调用运行并等待结果返回,是同步的。
下面用两个例子来展示他们的区别:
(1)
- public class SwingDemoInvokeAndWait {
- public static void main(String[] argv) throws InterruptedException, InvocationTargetException {
- SwingUtilities.invokeAndWait(new Runnable() {
- @Override
- public void run() {
- constructUI();
- }
- });
- 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.invokeAndWait(doHelloWorld);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("Finished on " + Thread.currentThread());
- }
- };
- 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(200, 150);
- frame.setVisible(true);
- bulletin.setForeground(Color.RED);
- }
- }
由于doHelloWorld是在invokeAndWait中被执行的,所以 一定会等待doHelloWorld方法的执行并返回,即”Hello World on”一定会在”Finished on”前显示出来。
(2)
- 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(200, 150);
- 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
文章名称:浅析Swing线程模型和EDT
分享链接:http://www.gawzjz.com/qtweb2/news42/7442.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联