Swing 的任务线程与 EDT 事件分发队列模型

举报
JavaEdge 发表于 2021/06/04 00:11:18 2021/06/04
【摘要】 1 现象及问题 在Swing程序中,经常能看到如下这种代码: 为何用invokeLater,而不直接调用呢? 大多数Swing的API非线程安全,不能在任意地方调用,应该只在EDT中调用。 Swing的线程安全靠事件队列和EDT来保证。 EventQueue的派发机制由单独的一个线程 - 事件派发线程(EDT)管理。 Swing将GUI请求放入一个事件队列中...

1 现象及问题

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

为何用invokeLater,而不直接调用呢?
大多数Swing的API非线程安全,不能在任意地方调用,应该只在EDT中调用。

Swing的线程安全靠事件队列和EDT来保证。

EventQueue的派发机制由单独的一个线程 - 事件派发线程(EDT)管理
Swing将GUI请求放入一个事件队列中执行。通过EDT,使得非线程安全的Swing函数库避开了并发问题。

3 Swing 中的线程分类

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

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

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

  • 任务线程(Worker Thread)

4 Swing 编程铁律

4.1 必须通过EDT刷新组件

从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误

4.2 禁止在EDT执行其他耗时操作

在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理

4.3 耗时操作放在独立的任务线程

通过SwingWorker启动。应使用独立的任务线程来执行耗时计算或输入输出密集型任务。

  • 比如同数据库通信
    访问网站资源、读写大树据量的文件。

任何干扰或延迟UI事件的处理只应出现在独立任务线程中。

  • 在初始化线程(即禁止在main方法中直接创建Frame,在初始化线程中应使用invokeLater初始化GUI)
  • 任务线程同Swing组件或其缺省数据模型进行的交互

都是非线程安全性操作。

通过SwingWorker类的管理,隔离EDT和任务线程,使它们各负其责

  • EDT 绘制和更新界面,并响应用户输入
  • 任务线程,执行和界面无直接关系的耗时任务和I/O密集型操作

5 事件队列

在计算机数据结构中,队列是一个特殊的数据结构。

  • 它是线性的
  • 元素是先进先出的,进入队列的元素必须从末端进入,先入队的元素先得到执行,后入队的元素等待前面的元素执行完毕出队后才能执行,队列的处理方式是执行完一个再执行下一个

队列与线程安全是无关的,不过要想将队列保证线程安全,只需要仿照生产者/消费者模式加上线程的等待/通知即可。

6 Swing 事件分发线程(EDT)

Swing的事件队列就类似事件队列,仅单一消费者,即一个事件分发线程。
除非你的程序停止,否则EDT会永不间断地徘徊在处理请求与等待请求之间。

  • Swing事件队列的实现机制图解

6.1 单一线程的事件队列的特性

  • 将同步操作转为异步操作
  • 将并行处理转换为串行顺序处理

6.2 EDT要处理所有GUI操作

  • 职责明确,任何GUI请求都应该在EDT中调用
  • 要处理的GUI请求非常多,包括窗口移动、组件自动重绘、刷新,它很忙。任何与GUI无关的处理不要由EDT执行,尤其是I/O耗时操作

7 Swing不是一个“安全线程”的API,为什么要这样设计

Swing的线程安全不是靠自身组件的API来保障,虽然repaint方法是这样,但是大多数SwingAPI是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT保证。

8 invoke系方法

对非EDT的并发调用需通过invokeLater()和invokeAndWait()使请求插入到队列中等待EDT去执行。

由于Swing本身非线程安全,如果你在其他线程访问和修改GUI组件,必须使用

8.1 SwingUtilities. invokeAndWait(runnable)


同步的,它被调用结束会立即阻塞当前线程,直到EDT处理完该请求。
一般用于取得Swing组件的数据。

8.2 SwingUtilities. invokeLater(runnable)

使 doRun.run() 在AWT事件分法线程上异步执行。所有待处理的AWT事件被执行后,就会发生这种情况。当应用程序线程需要更新GUI时,应使用此方法。

在下面的示例中,invokeLater调用将Runnable对象doHelloWorld排队在事件分配线程上,然后打印一条消息。

 Runnable doHelloWorld = new Runnable() { public void run() { System.out.println("Hello World on " + Thread.currentThread()); }
 };

 SwingUtilities.invokeLater(doHelloWorld);
 System.out.println("This might well be displayed before the other message.");

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果从事件分发线程(例如,从JButton的ActionListener)调用invokeLater,则 doRun.run 仍将延迟,直到处理完所有未决事件。请注意,如果doRun.run 引发未捕获的异常,则事件分发线程将展开(而不是当前线程)。

从1.3版本开始,此方法只是java.awt.EventQueue.invokeLater()的封面。

与Swing的其余部分不同,可以从任何线程调用此方法。

  • 准则
    不能在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); } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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

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

文章来源: javaedge.blog.csdn.net,作者:JavaEdge.,版权归原作者所有,如需转载,请联系作者。

原文链接:javaedge.blog.csdn.net/article/details/105700087

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。