前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Swing 的任务线程与 EDT 事件分发队列模型

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

作者头像
JavaEdge
发布2020-05-26 16:58:43
9090
发布2020-05-26 16:58:43
举报
文章被收录于专栏:JavaEdgeJavaEdge

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排队在事件分配线程上,然后打印一条消息。

代码语言:javascript
复制
 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.");

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

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

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

  • 准则 不能在EDT中被调用,否则程序会抛出Error,请求也不会去执行。看源码:
代码语言:javascript
复制
 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有意地避免了这类情况的发生。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 现象及问题
  • 3 Swing 中的线程分类
  • 4 Swing 编程铁律
    • 4.1 必须通过EDT刷新组件
      • 4.2 禁止在EDT执行其他耗时操作
        • 4.3 耗时操作放在独立的任务线程
        • 5 事件队列
        • 6 Swing 事件分发线程(EDT)
          • 6.1 单一线程的事件队列的特性
            • 6.2 EDT要处理所有GUI操作
            • 7 Swing不是一个“安全线程”的API,为什么要这样设计
            • 8 invoke系方法
              • 8.1 SwingUtilities. invokeAndWait(runnable)
                • 8.2 SwingUtilities. invokeLater(runnable)
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档