在Swing程序中,经常能看到如下这种代码:

SwingUtilities.invokeLater(new Runnable(){
  @Override
    public void run() {
      textField1.setText("element changed!");
      textField1.setForeGround(Color.RED);
    }
});  

为什么要用SwingUtilities.invokeLater,而不直接调用呢?因为大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。

EventQueue的派发机制由单独的一个线程管理,这个线程称为事件派发线程(EDT)。和其他很多桌面API一样,Swing将GUI请求放入一个事件队列中执行。

       通过EDT,使得不具备线程安全的Swing函数库避开了并发访问的问题。

背景概念

要了解EDT,首先需要了解一些背景概念:

同步与异步:

       同步是程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此之前程序被block住直到请求完成。

       异步是当前程序发起请求后立即返回,当前程序不会立即处理该事件并等待处理的结果,请求是在稍后的某一时间才被处理。

串行与并行:

       串行是指多个要处理请求顺序执行,处理完一个再处理下一个;

       并行可以理解为并发,是同时处理多个请求(实际上我们只能理解为是这样,特别是CPU数目少于线程数的机器而言,真正意义的并发是不存在的,各个线程只是断断续续地交替地执行)。

下图演示了串行与并行的机制。可以这么说,在引入多线程之前,对于同一进程或者程序而言执行的都是串行操作。

串行:

并行:

生产者/消费者模式:

可以想象这样一副场景,某车间的一条传送带,有一个或多个入口不断产生待加工的货物,这种不断产生货物的称为生产者;传送带的末端是一个或多个工人在加工货物,称作消费者。有时由于传送带上没有足够的货物使得某一工人暂时空闲,有时又由于部分货物需加工的时间较长出现传送带上待加工的货物堆积。

如果用Java实现一个简单的生产者消费者模型,利用线程的等待/通知机制很容易实现。

public class SyncQueue {  

  private List buffer = new ArrayList();  

  //消费
  public synchronized Object pop() {
    Object e;
    while (buffer.size() == 0) {
      try {
        wait();
      } catch (InterruptedException e1) {
        // ignore it
      }
    }
    e = buffer.remove(0);
    return e;
  }  

  //生产
  public synchronized void push(Object e) {
    notifyAll();
    buffer.add(e);
  }
}  
 

事件队列:

在计算机数据结构中,队列是一个特殊的数据结构。其一、它是线性的;其二、元素是先进先出的,也就是说进入队列的元素必须从末端进入,先入队的元素先得到执行,后入队的元素等待前面的元素执行完毕出队后才能执行,队列的处理方式是执行完一个再执行下一个。

队列与线程安全是两个不同的概念,如果要将队列加上线程安全的特性,只需要仿照上述生产者/消费者加上线程的等待/通知即可

Swing程序中的线程

一个Swing程序中一般有下面三种类型的线程:

  1. 初始化线程(Initial Thread)
  2. UI事件调度线程(EDT)
  3. 任务线程(Worker Thread)


        初始化线程
:每个程序必须有一个main方法,这是程序的入口。该方法运行在初始化或启动线程上。初始化线程读取程序参数并初始化一些对象。在许多Swing程序中,该线程主要目的是启动程序的图形用户界面(GUI)。创建UI的点,也就是程序开始将控制权转交给UI时的点。一旦GUI启动后,对于大多数事件驱动的桌面程序来说,初始化线程的工作就结束了。

      EDT:Swing程序只有一个EDT,该线程负责GUI组件的绘制和更新,通过调用程序的事件处理器来响应用户交互。所有事件处理都是在EDT上进行的,程序同UI组件和其基本数据模型的交互只允许在EDT上进行,所有运行在EDT上的任务应该尽快完成,以便UI能及时响应用户输入。
      
         Swing编程时应该注意:

  1. 从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误。——必须通过EDT刷新组件
  2. 在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理。——不能在EDT中执行其他耗时操作
  3. 应使用独立的任务线程来执行耗时计算或输入输出密集型任务,比如同数据库通信、访问网站资源、读写大树据量的文件。——耗时操作应放到独立的任务线程中,通过SwingWorker启动

总之,任何干扰或延迟UI事件的处理只应该出现在独立任务线程中;在初始化线程(也就是说不能在main方法中直接创建Frame!在初始化线程中应该使用invokeLater方法初始化程序界面)或任务线程同Swing组件或其缺省数据模型进行的交互都是非线程安全性操作。
       SwingWorker类帮你管理任务线程和EDT之间的交互,尽管SwingWorker不能解决并发线程中遇到的所有问题,但的确有助于分离SwingEDT和任务线程,使它们各负其责:对于EDT来说,就是绘制和更新界面,并响应用户输入;对于任务线程来说,就是执行和界面无直接关系的耗时任务和I/O密集型操作。

Swing事件分发线程

       Swing的事件队列就类似于上述的事件队列(基本原理相似,但是Swing内部实现会做些优化),说它是单线程图形工具包指的是仅有单一消费者,也就是常说的事件分发线程(EDT),一般来讲,除非你的应用程序停止,否则EDT会永不间断地徘徊在处理请求与等待请求之间。

下图是Swing事件队列的实现机制:

很显然,如果在加工某一个货物上花费很长的时间,那么后续的货物只好等待。

对于单一线程的事件队列来说有两个非常突出的特性:

  1. 将同步操作转为异步操作。
  2. 将并行处理转换为串行顺序处理。

EDT要处理所有GUI操作,它是职责分明且非常忙碌的。也就是说你要记住两条原则:

  1. 职责分明,任何GUI请求都应该在EDT中调用。
  2. 需要处理的GUI请求非常多,包括窗口移动、组件自动重绘、刷新,它很忙,所以任何与GUI无关的处理不要由EDT来负责,尤其是I/O这种耗时的操作。

上面说过Swing不是一个“安全线程”的API,为什么要这样设计?再回看上图就会明白:Swing的线程安全不是靠自身组件的API来保障,虽然repaint方法是这样,但是大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。

invokeLater和invokeAndWait

由于Swing自身不是线程安全,如果你在其他线程访问和修改GUI组件,那么你必须要使用SwingUtilities. invokeAndWait(runnable), SwingUtilities. invokeLater(runnable)。也就是说对非EDT的并发调用需通过invokeLater()和invokeAndWait()使请求插入到队列中等待EDT去执行

  • invokeLater(runnable)方法是异步的,它会立即返回,具体何时执行请求并不确定,所以命名invokeLater是稍后调用。
  • invokeAndWait(runnable)方法是同步的,它被调用结束会立即block当前线程(调用invokeAndWait的那个线程)直到EDT处理完那个请求。invokeAndWait一般的应用是取得Swing组件的数据。

 invokeAndWait有非常重要的一条准则是:它不能在EDT中被调用,否则程序会抛出Error,请求也不会去执行。看源码:

 
public static void invokeAndWait(Runnable runnable)
            throws InterruptedException, InvocationTargetException {  

       //不能在EDT中调用invokeAndWait
       if (EventQueue.isDispatchThread()) {
           throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
       }  

class AWTInvocationLock {}
       Object lock = new AWTInvocationLock();  

       InvocationEvent event =
           new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock,
            true);  

       synchronized (lock) {
           //添加进事件队列
           Toolkit.getEventQueue().postEvent(event);
           //block当前线程
           lock.wait();
       }  

       Throwable eventThrowable = event.getThrowable();
       if (eventThrowable != null) {
           throw new InvocationTargetException(eventThrowable);
       }
   }  

如果invokeAndWait在EDT中调用,那么首先将请求压进队列,然后EDT便被block,等待请求结束通知它继续运行。

而实际上请求将永远得不到执行,因为它在等待队列的调度使EDT执行它,这就陷入一个僵局:EDT等待请求先执行,请求又等待EDT对队列的调度。彼此等待对方释放锁是造成死锁的四类条件之一。Swing有意地避免了这类情况的发生。

原文地址:跳转

【转】Swing 与EDT线程的更多相关文章

  1. Swing中的线程并发处理

    理论解释见官方的文档: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html 一个Swing程序中一般有下面三种 ...

  2. Java自学-图形界面 Swing中的线程

    Swing中的线程 步骤 1 : 三种线程 在Swing程序的开发中,需要建立3种线程的概念 初始化线程 初始化线程用于创建各种容器,组件并显示他们,一旦创建并显示,初始化线程的任务就结束了. 事件调 ...

  3. swing中的线程

    1. 初始化线程 初始化线程用于创建各种容器,组件并显示他们,一旦创建并显示,初始化线程的任务就结束了. 2. 事件调度线程(单线程:只有一个线程在负责事件的响应工作.) 通过事件监听的学习,我们了解 ...

  4. java swing调试时线程显示名字

    一般有一个默认名字 但是具体运行到哪一个线程,需要猜 为了节约时间,提高效率 可以给线程写个中文名(因为默认就是英文,写中文,一眼就能挑出来) 以RTC定时器为例子 final TimerRtc ti ...

  5. swing线程机制

    在介绍swing线程机制之前,先介绍一些背景概念. 背景概念 同步与异步:     同步是指程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此之前程序被阻塞(block)直到请求完成 ...

  6. 【Swing】理解Swing中的事件与线程

    talk is cheap , show me the code. Swing中的事件 事件驱动 所有的GUI程序都是事件驱动的.Swing当然也是. GUI程序不同于Command Line程序,一 ...

  7. 使用泛型SwingWorker与EDT事件分发线程保持通讯

    为什么要使用SwingWorker 在swing开发中,如果一个应用程序,执行一些任务,需要大量的时间来完成,比如下载一个大文件或执行一个复杂的数据库查询. 我们假设这些任务是由用户使用一个按钮触发的 ...

  8. 从swing分发线程机制上理解多线程[转载]

    本文参考了 http://space.itpub.net/13685345/viewspace-374940,原文作者:javagui 在多线程编程当中,总会提到图形编程,比如java中的swing, ...

  9. 《FilthyRichClients》读书笔记(一)-SwingのEDT

    <FilthyRichClients>读完了前几个章节,现将我的体会结合工作以来从事Swing桌面开发的经验,对本书的一些重要概念进行一次 分析,对书中的一些遗漏与模糊的地方及时补充,同时 ...

随机推荐

  1. SU suphasevel命令学习

  2. BZOJ2837 : 小强的形状

    离散化后通过树状数组求出: b[i]为i之前比它小的. c[i]为i之前比它大的=i-1-i之前小于等于它的. d[i]为i之后比它小的. e[i]为i之后比它大的=n-i-f[i]. f[i]为i之 ...

  3. javascript生成n至m的随机整数

    摘要: 本文讲解如何使用js生成n到m间的随机数字,主要目的是为后期的js生成验证码做准备. Math.random()函数返回0和1之间的伪随机数,可能为0,但总是小于1,[0,1) 生成n-m,包 ...

  4. 【BZOJ】2820: YY的GCD(莫比乌斯)

    http://www.lydsy.com/JudgeOnline/problem.php?id=2820 此题非常神! 下文中均默认n<m 首先根据bzoj1101的推理,我们易得对于一个数d使 ...

  5. List 中对象属性排序

    有几个方法可以实现:让 Student 实现Comparable接口,或是实例化一 个比较器, 现在用 Comparator 比较器实例来做一个:ComparableTest.java import  ...

  6. Wps 方框里面加勾

    1.选择插入-->选择符号 2.选择更多 3.选择 字体Wingdings 然后下拉到最后.就看到了

  7. mybatis配置oracle的主键自增长

    引用自:https://hacpai.com/article/1405392025960 mysql.sqlserver等数据库本身带有主键自增长像auto_increment的功能可以直接使用 us ...

  8. Export-XLSX PowerShell generate real Excel XLSX files without Excel and COM

    http://gallery.technet.microsoft.com/scriptcenter/Export-XLSX-PowerShell-f2f0c035

  9. [ZZ] Understanding 3D rendering step by step with 3DMark11 - BeHardware >> Graphics cards

    http://www.behardware.com/art/lire/845/ --> Understanding 3D rendering step by step with 3DMark11 ...

  10. Web 在线文件管理器学习笔记与总结(10)查看文件夹中的内容

    ① 读取文件夹大小 a. 封装计算文件夹大小的函数 b.  打开文件夹 c. 循环判断文件夹下的内容是文件还是文件夹,如果是文件,则累积相加文件的大小:如果是文件夹,则递归调用该函数 注意两个问题: ...