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 }


     

     

  • 相关阅读:
    作业七随笔。。
    Jquery 图片走马灯效果原理
    参与招聘面试工作之简历与仪容篇
    无聊系列 C#中一些常用类型与java的类型对应关系
    关于ASP.NET MVC 中JsonResult返回的日期值问题
    最近参与招聘面试的工作总结
    Unix时间戳转日期时间格式,C#、Java、Python各语言实现!
    MVC 拦截器
    Python参考书籍(转载)
    PEP 8风格指南(转载)
  • 原文地址:https://www.cnblogs.com/jilodream/p/4990043.html
Copyright © 2011-2022 走看看