`
tianjie123
  • 浏览: 19595 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论
阅读更多
《FilthyRichClients》读书笔记-SwingのEDT

《FilthyRichClients》读完了前几个章节,现将我的体会结合工作以来从事Swing桌面开发的经验,对本书的一些重要概念进行一次分析,对书中的一些遗漏与模糊的地方及时补充,同时使读者消除长期以来“Swing性能低、界面丑陋”诸如此类的旧观念。读书笔记仅谈谈我对Swing的理解,难免会犯错误,还望广大读者指教。
    书中第二章-Swing渲染基本原理 中对Swing的线程做了系统地介绍。相比其他同类Swing教程,已经讲得非常深入了。但是如果读者之前对线程的掌握程度有限,尤其是编写代码比较随意的coder们,动辄就大量编写类似下面这样的代码:
jButton1.addActionListener(new ActionListener(){
   public void actionPerformed(ActionEvent e) {
    // TODO
   }
  });
这样的代码可能是netBeans这样的工具生成的“杰作”。但是如果这个人再懒惰一点,可能会直接在TODO下面写上长长一堆代码,还伴随着不可预知的 I/O操作,很多人指责界面被僵住是Swing性能的问题。在新式的JDK中,Swing已经在性能方面改进了很多,完全可以这么说:与应用程序自身的业务计算相比,界面上的耗时可以忽略。但是如果上述恶习改不掉的话,Swing永远“快”不起来,SWT也同样如此,因为它们都是单线程图形工具包。
    书上有这样一段话:“EventQueue的派发机制由单独的一个线程管理,这个线程称为事件派发线程(EDT)”。和其他很多桌面API一样,Swing将GUI请求放入一个事件队列中执行。如果不明白什么是事件队列、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);
}
}
在JDK 5中新出现了许多具有并发性的数据结构在java.util.concurrent包中,它们适合于特殊的场合,本帖不作解释。

    事件队列:在计算机数据结构中,队列是一个特殊的数据结构。其一、它是线性的;其二、元素是先进先出的,也就是说进入队列的元素必须从末端进入,先入队的元素先得到执行,后入队的元素等待前面的元素执行完毕出队后才能执行,队列的处理方式是执行完一个再执行下一个。队列与线程安全是两个不同的概念,如果要将队列加上线程安全的特性,只需要仿照上述生产者/消费者加上线程的等待/通知即可。

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



很显然,如果在加工某一个货物上花费很长的时间,那么后续的货物只好等待。对于单一线程的事件队列来说有两个非常突出的特性:一、将同步操作转为异步操作。二、将并行处理转换为串行顺序处理。

如果你能理解上述图,那么你就应该意识到:EDT要处理所有GUI操作,它是职责分明且非常忙碌的。也就是说你要记住两条原则:一、职责分明,任何GUI请求都应该在EDT中调用。二、需要处理的GUI请求非常多,包括窗口移动、组件自动重绘、刷新,它很忙,所以任何与GUI无关的处理不要由EDT来负责,尤其是I/O这种耗时的操作。
    书中还讲到Swing不是一个“安全线程”的API,为什么要这样设计,再回看上图就会明白:Swing的线程安全不是靠自身组件的API来保障,虽然repaint方法是这样,但是大多数Swing API是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。
    invokeLater和invokeAndWait:前文提到,Swing自身不是线程安全,对非EDT的并发调用需通过 invokeLater(runnable)和invokeAndWait(runnable)使请求插入到队列中等待EDT去执行。 invokeLater(runnable)方法是异步的,它会立即返回,具体何时执行请求并不确定,所以命名invokeLater是稍后调用。invokeAndWait(runnable)方法是同步的,它被调用结束会立即block当前线程(调用invokeAndWait的那个线程)直到EDT处理完那个请求。invokeAndWait一般的应用是取得Swing组件的数据,例如取得JSlider组件的当前值:
public class Task implements Runnable {
private JSlider slider;
private int value;
public Task() {
  //slider = ...;
}
@Override
public void run() {
  try {
   Thread.sleep(1000); // 有意停住1秒
  } catch (InterruptedException e) {
  }
  value = slider.getValue();
}
public int getValue() {
  return value;
}
}
而外部非EDT线程可以这样调用:
Task task = new Task();
  try {
   EventQueue.invokeAndWait(task);
  } catch (InterruptedException e) {
  } catch (InvocationTargetException e) {
  }
  int value = task.getValue();
当线程运行到EventQueue.invokeAndWait(task)时会立即被block至少1秒,待invokeAndWait返回时已经可以安全地取到值了。invokeAndWait被这样命名也反映了使用的意图:调用并等待结果。invokeAndWait有非常重要的一条准则是它不能在 EDT中被调用,否则程序会抛出Error,请求也不会去执行。

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

        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); //添加进事件队列
            lock.wait(); // block当前线程
        }

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


为什么要有这样一条限制?结合前文不难得出-防止死锁。如果invokeAndWait在EDT中调用,那么首先将请求压进队列,然后EDT便被 block(因为它就是调用invokeAndWait的当前线程)等待请求结束通知它继续运行,而实际上请求将永远得不到执行,因为它在等待队列的调度使EDT执行它,这就陷入一个僵局-EDT等待请求先执行,请求又等待EDT对队列的调度。彼此等待对方释放锁是造成死锁的四类条件之一。Swing有意地避免了这类情况的发生。

    书中也提到了同步的绘制请求,作为队列,一条基本原则就是先进先出。那么paintImmediately到底是怎样的呢?显然这个调用请求不会稍后去执行,也就是说不会插入到队列的末尾等到排在它前面的请求执行完再去执行它,而是“破坏”顺序性原则优先去执行,前面提到,Swing的事件队列相对基础的同步队列做了很多优化,那么这么说它是否被插入到队列最前面呢,也就是0这个位置?貌似也不是,书上说“已经在EDT中调用的方法中间...”,那么就是比当前正在处理的绘制请求还要优先,因为它是当前绘制请求的一部分,所以当前绘制请求(EDT正在处理的那个请求)要等它处理完成后再继续处理。(好好体会吧)
    SwingWorker:推荐一篇Blog,http://blog.sina.com.cn/s/blog_4b6047bc010007so.html,作者是原Sun中国工程研究院的陈维雷先生,他对Swing的造诣非浅,他的Blog中有3篇介绍这一主题的文章,详尽程度要比该书详细得多。

    最后,谈一下理解EDT对设计模式的帮助。通过上述对事件队列和EDT的分析,有这样一种体会:事件队列是一个非常好的处理并发设计模型,不仅 Swing用它来处理后台,Java的很多地方都在用,只不过对于处理服务器端的并发请求有多个处理线程在等候处理请求,也就是常说的线程池。而对于单用户的桌面应用,单线程调用要比多现成API更简单,“Swing后台这样做是为了保证事件的顺序和可预见性”,而且相对于服务器,客户端桌面层的请求要少得多,所以单线程就足够应对了。
单一Thread化的访问:

通过EDT,使得不具备线程安全的Swing函数库避开了并发访问的问题。如果你也有一个不具备thread安全性的函数库并想在multithreaded环境下使用应该怎么办?只要你是从单一的thread来访问这个函数库,程序就不会遭遇到任何数据同步的问题。


1
0
分享到:
评论

相关推荐

    实验4 线程编程实验.doc

    本实验的主要目的是了解多线程的概念和应用,掌握线程的创建方法和任务类,理解事件分发线程机制和线程池的概念及应用,以及线程的同步和异步。 1. 多线程的概念 线程是一条执行路径,是程序执行时的最小单位,是...

    事件分发_C#_事件分发_serious791_

    此外,多线程编程、网络通信等领域也广泛利用事件来处理异步操作的结果。 总之,C#中的事件分发是一个强大的特性,它基于委托和事件的概念,实现了对象间的松耦合通信。通过理解和掌握这一机制,开发者可以构建更加...

    Android应用程序输入事件处理机制

    在Android应用程序中,有一类...这个PPT讲Android应用程序输入事件的分发和处理过程,主要涉及到输入管理InputManager、输入事件监控线程InputReader、输入事件分发线程InputDispatcher,以及应用程序主线程消息循环。

    Java开发中的线程安全选择与Swing

    Swing组件和大多数Swing方法并不是线程安全的,这意味着它们只能在事件分发线程(Event Dispatch Thread, EDT)中被安全地访问和修改。如果在非EDT线程中直接操作Swing组件,则可能导致应用程序崩溃或行为异常。 ##...

    贪吃蛇的多线程java程序

    线程管理对于GUI尤为重要,因为GUI更新必须在事件分发线程(Event Dispatch Thread, EDT)中进行,以保持界面的响应性。 8. **异常处理** - 多线程环境下,需要考虑线程中断和异常处理。例如,当游戏结束时,可能...

    基于swing的多线程聊天室

    3. Event Dispatch Thread(事件分发线程):Swing是单线程的,所有对组件的操作必须在EDT上进行,以确保界面的同步更新。 二、多线程技术 1. 用户界面线程:主GUI运行在EDT上,负责处理用户输入和显示UI更新。 2. ...

    关于JAVA中事件分发和监听机制实现的代码实例

    在Swing中,事件分发遵循“事件调度线程”模型,确保所有UI更新都在同一线程中进行,避免了线程安全问题。 6. **事件适配器**:有时,我们可能只需要实现监听器接口中的一部分方法。Java为此提供了事件适配器类,如...

    安全的编写多线程的_Java_应用程序

    - **多线程问题**: GUI组件只能在事件分发线程(EDT)中更新,否则会导致异常。 - **解决方案**: 使用`SwingWorker`类或`ExecutorService`进行异步任务处理,确保GUI组件更新操作在EDT中执行。 **2. 典型案例分析**...

    java面板多线程发牌程序

    考虑到GUI的更新需要在事件分发线程(Event Dispatch Thread, EDT)中进行,以保持界面的同步,发牌线程可能需要通过`SwingUtilities.invokeLater()`或`invokeAndWait()`方法来调度,确保在正确的线程上执行。...

    java线程之滚动的文字

    为了确保界面的更新是安全的,我们需遵守Java Swing的多线程规则,即所有的UI更新操作必须在事件分发线程(Event Dispatch Thread, EDT)上执行。因此,我们需要使用`SwingUtilities.invokeLater()`或`SwingWorker`...

    Swing线程之SwingUtilities.invoke

    Swing线程机制是Swing库中的关键概念,它确保了所有对Swing组件的操作都在正确的线程——事件分发线程(Event Dispatch Thread,简称EDT)上执行,以避免线程安全问题和界面更新不一致的情况。`SwingUtilities....

    设计滚动字演示线程状态及改变方法

    需要注意的是,在更新 `JTextField` 的内容时,必须确保所有UI更新都在事件分发线程(EDT)中执行,以避免UI组件的并发访问问题。 #### 线程状态监控 在 `Runnable` 接口中实现的方法中,可以通过检查线程是否被中断...

    Java事件传递技术

    事件分发线程会从队列中取出事件并分发给相应的监听器。 7. **AWT和Swing事件模型**:Java有两种主要的GUI库,分别是AWT(Abstract Window Toolkit)和Swing。AWT使用冒泡模型,事件从最具体的组件向上冒泡到不那么...

    java贪吃蛇源码多线程

    具体来说,游戏的主循环(如蛇移动、食物生成等)可以在单独的线程中运行,而GUI更新则在事件分发线程中处理: ```java // 假设这是游戏逻辑线程 Thread gameLogicThread = new Thread(() -> { while (gameRunning...

    使用Netty4实现多线程的消息分发

    在本文中,我们将深入探讨如何利用 Netty 4 实现多线程的消息分发,这对于构建分布式系统、游戏服务器或者任何需要高效处理并发连接的应用尤其重要。 一、Netty 框架简介 Netty 是由 JBoss 提供的一个开源项目,它...

    Swing中的线程研究.pdf

    - **事件分发线程 (EDT)**:当主线程显示第一个窗口时,会创建EDT。EDT是负责处理用户交互、事件处理和界面渲染的线程。所有的事件回调(如`actionPerformed`)和绘图操作(如`paintComponent`)都在这个线程中执行...

    Java线程处理

    Swing使用事件分发线程(EDT)来处理所有的GUI事件,这意味着所有的GUI更新必须在EDT中进行。为了确保UI的响应性,长时间运行的任务应放在非EDT线程中执行。 通过以上内容,我们可以了解到Java线程处理的关键概念和...

    java多线程聊天室(使用swing)

    Swing是线程不安全的,因此所有的UI操作必须在事件分发线程(Event Dispatch Thread, EDT)上进行,以避免界面组件的同步问题。 2. **工作线程**:处理非UI任务,如接收和发送聊天消息。通过使用Thread类或者...

Global site tag (gtag.js) - Google Analytics