zoukankan      html  css  js  c++  java
  • Java多线程开发系列之番外篇:事件派发线程---EventDispatchThread

    事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点。今天我们在多线程开发中,穿插进来这个线程。分别从线程的来由、原理和使用方法三个方面来学习事件派发线程。

     

    一、事件派发线程的前世今生

    事件(Event)派发(Dispatch)线程(Thread)简写为EDT,也就是各个首字母的简写。在一些书或者博客里边也将其译为事件分发线程、事件调度线程。巴拉巴拉,总之,知道这些名字就行。笔者认为这里翻译成派发更准确点。

    熟悉Swing和awt编程的小伙伴对事件派发线程应该都不陌生。如果你提反对意见的话,只能说明你对Swing和awt编程还不够熟悉。

    事件派发线程诞生的故事背景是这样的:

    界面上各个控件对象都有保存自己的数据变量。如果出现多线程操作就会出现很多问题,诸如数据变脏,数组越界,空引用等等问题。

    举个栗(例)子

    线程A发现panel中还有数据要显示(check data),于是调用滚动条向下滚动。这时,panel内部要调用数据中为展示的数据用来显示。可是在展示的过程中,线程发生了切换。由其它线程B删掉了需要展示的数据,这时线程A再次被唤醒继续运行,显示接下来的内容。由于已经过了Check Data的逻辑。所以接下来就要调用已经不存在的数据用来展示。最后就会出现各种奇怪的问题。(如果你没看懂,就理解成各个线程最终都在操作控件的数据源,则控件在显示的时候就可能会出现异常)。

    通常来说解决这种多线程冰法问题方式就是"锁"或者"同步"。

    当时Sun公司的Swing小组最初也是这个思路,但是让Swing小组最终改变主意的由于接下来的两个原因:

    1、数据同步在保证线程安全的同时,很耗费时间。UI最重要的就是界面响应速度,毕竟谁也不想面对一个幻灯片在操作。

    2、Swing小组调查了其他小组在线程安全的用户界面工具包方(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )面的经验后,发现结果并不是那么的美好:开发线程安全包的工程师被各种同步操作搞晕了头,程序经常发生死锁。

    就此,Swing小组决定使用单一线程来控制整个界面的控件绘制。这个线程就是事件派发线程。

    事件派发线程就是这样被创造出来的:

     

    二、事件派发线程的原理

    事件派发线程的原理其实非常的简单,在界面后台始终只有这一个线程在工作,这个线程就是事件派发线程。当你有需要操作界面的行为时,将这些行为添加到事件派发线程的事件队列中,事件派发线程会依次执行这个队列中的请求。

    这有点像单核cpu进行多线程操作的场景,不同的地方是,这时候事件派发线程的作用是单核cpu。

    具体内容可以查看下图(图片来源于网络)

     

    各个线程将GUI请求排成队列,然后由事件派发线程依次执行这个队列中的请求。

    如果从设计模式的角度来看,这个地方是一个典型的"消费者"模式,有兴趣的小伙伴可以查阅下相关的设计模式内容,这里就不展开赘述了。

    了解了事件派发线程原理之后,我们会发现这样一个问题:

    eventQueue中的事件没有轻重缓急之分,是遵循FIFO的原则的。那么如果前边的请求非常耗时,需要大量的db请求、IO等操作,那么后边的请求只能一直等待。

    当初舍弃'同步'是为了快,现在界面还是会卡死,违背了初衷。

    基于以上,Swing开发人员提出了两点在使用事件派发线程时需要遵守的原则:

    1、只有事件派发线程可以调用Swing组件,其他线程都离组件远远的。(有些地方称这条准则为单一线程规则single-thread rule)

    2、如果某一个GUI请求非常耗时,就不要把这个请求发送给事件派发线程。直到这个请求通过其他线程处理之后,最后的少部分界面请求再发送给事件派发线程。

     

    三、怎么使用事件派发线程

    上面说了非常多,但是不知道怎么使用事件派发线程,则上边所述也就没有什么用了。

    首先,前文中提到了事件派发线程是启动GUI后,(其实这里还存在有一个初始化线程,短暂的启动GUI的生命过程)系统自动启动的一个线程。

    所以我们就不用手动创建和运行线程了。我们要做的就(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )是向事件派发线程中添加各种GUI请求到eventQUEUE中去即可。

     

    Swing为我们提供了三个常用的API

    1 SwingUtilities.invokeAndWait(Runnable runnable)//同步请求,发送请求的线程会一直等到EDT执行完毕自己的请求后,才会继续执行剩余代码;
    2 
    3 SwingUtilities.invokeLater(Runnable runnable)//异步请求,发送请求的线程在请求添加到EDT的eventQUEUE后,才会执行剩余代码;
    4 
    5 SwingUtilities.isEventDispatchThread()//判断当前线程是否为事件派发线程。

    一般来说在编写请求代码的时候,最好先判断下执行线程是否为事件派发线程,然后在选择是直接执行还是添加到事件队列中。

    值得注意的是这里会存在一个问题:

    就是如果当前线程就是事件派发线程时,是不允许其执行invokeAndWait()同步方法的。

    这是由于如果出现这种情况EDT就会停顿(wait)在这个点,等待EDT去执行添加的请求,同时由于EDT已经停顿在了这个点,那么EDT也就不会去处理eventQUEUE中的请求,形成了一种死锁。

    好在JDK中已经对这种情况做了校验,所以上面没太看懂的同学无需太在意,只要记住结果即可:

     

    最后我们再来一个实际工作中代码的例子

     1 if(SwingUtilities.isEventDispatchThread())
     2 {
     3     OptTree.RefreshNode();
     4 }
     5 else
     6 {
     7     SwingUtilities.invokeAndWait(new Runnable()
     8     {
     9         @Override
    10         public void run()
    11         {
    12             OptTree.RefreshNode();
    13         }
    14     });
    15 }


     

     

  • 相关阅读:
    Win8系统 Python安装
    一些安卓开源框架整理
    Android 媒体键监听以及模拟媒体键盘的实现 demo
    android View 自动 GONE 问题
    Android 定时器TimerTask 简单使用
    关于Android studio 相对 eclipse 优点
    Java序列化与反序列化
    android shape的使用 边框
    Android Studio 修改 包名 package name
    Android WebView Long Press长按保存图片到手机
  • 原文地址:https://www.cnblogs.com/jilodream/p/4990043.html
Copyright © 2011-2022 走看看