博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析Swing线程模型和EDT
阅读量:5334 次
发布时间:2019-06-15

本文共 5188 字,大约阅读时间需要 17 分钟。

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

   以前,我会这么写:

 
ContractedBlock.gif
ExpandedBlockStart.gif
Code
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的《》文中总结了两点:

    (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这块就是线程安全的。

所以前面的代码应该修改成这样:

 
ContractedBlock.gif
ExpandedBlockStart.gif
Code
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);
  }
}

但是除了线程安全外,还有两点我们需要注意和理解:

1.那种特别耗时的任务不应该把它放到EDT中,否则这个应用程序会变得无法响应。因为EDT会忙于执行你的耗时的任务,而无暇顾及其他GUI事件。(没办法啊,那么多活堆在那,EDT一个人挑,做男人难啊,做EDT更难!Open-mouthed

 

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)

 
ContractedBlock.gif
ExpandedBlockStart.gif
Code
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(
200150);
        frame.setVisible(
true);
        bulletin.setForeground(Color.RED);
    }
}

 Yin Yang由于doHelloWorld是在invokeAndWait中被执行的,所以 一定会等待doHelloWorld方法的执行并返回,即”Hello World on”一定会在”Finished on”前显示出来.

(2)

 
ContractedBlock.gif
ExpandedBlockStart.gif
Code
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);
    }
}

 

Yin Yang由于doHelloWorld是在invokeLater中被执行的,因而“Finished on”有可能出现在其他信息的前面比如”Hello World On”.

 

 

 

 

 

 

参考资料:

(1)

(2)

(3)

(4)

转载于:https://www.cnblogs.com/ChrisWang/archive/2009/09/16/Swing-Single-Thread-Queue-Mode-And-Event-Dispatch-Thread.html

你可能感兴趣的文章
强化学习第七章
查看>>
关于vs code和markdown
查看>>
dsjxtjc第一次实验
查看>>
某手游智能反外挂产品原理浅析
查看>>
基于设备指纹零感验证系统
查看>>
IaaS、PaaS和SaaS最浅显易懂的解释
查看>>
VMware上安装ubuntu后忘记密码解决办法(密码重置,亲测有效)
查看>>
KETTLE——初见KETTLE
查看>>
KETTLE——(一)资源库
查看>>
KETTLE——(二)数据抽取
查看>>
KETTLE——(三)数据输出
查看>>
KETTLE——(例)简单的字段转换
查看>>
关于Tomcat的浅谈
查看>>
Pycharm 配置文件模版
查看>>
oracle的sql 记录
查看>>
获取配置文件工具类
查看>>
java集合
查看>>
oracle 如何解锁表
查看>>
java.lang.Object类(JDK1.7)
查看>>
Java代理模式
查看>>