热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

J2SE5.0中用Executor灵活处理事件下发

内容:[J2SE5.0]用Executor灵活处理事件下发作者:AndrewThompson译者:xMatrix版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标
内容: [J2SE5.0]用Executor灵活处理事件下发
作者:Andrew Thompson
译者:xMatrix
版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Andrew Thompson;xMatrix
原文地址:http://www.onjava.com/pub/a/onjava/2005/03/23/executors.html
中文地址:http://www.matrix.org.cn/resource/article/43/43985_Executor.html
关键词: Executor J2SE5.0
每个JAVA开发人员都熟悉异步事件下发的EventListener模式。许多人也写过用来管理侦听器和下发事件给其他组件的样板代码。侦听器是简单、通用、灵活和容易实现的,但他涉及到其他开发人员写的代码,这可能引起问题:
1、一个低效的侦听器可能花费太长的时间来处理事件,这使得其他侦听器等待并可能引起死锁。
2、在你派发事件给侦听器器的时候,你可以控制下发的线程。但通常实现侦听器的开发人员不能控制事件如何被下发到他们的代码。
3、在一个多媒体应用中,如何同步GUI事件(如动画和用户交互)和其他异步事件流(如语音、声音和视频)不那么清晰。
这篇文章使用Doug Lea的Executor(现在是J2SE 5.0的一部分)使得事件派发更加灵活。你可以使用这种思想在以下方面:
1、允许开发人员使用你的组件作为事件派发策略的插件,这样就可以自定义事件的派发方式。
2、使不同的侦听器相互独立,因此一个低效的侦听器就不会影响其他的了。
3、多元化来自不同异步资源的事件流。
JAVA1.4兼容性注意:这篇文章使用J2SE 5.0的泛型来排除事件和侦听器器的转换。Dispatchable Event Library也包含一个非泛型的版本可以运行在JDK 1.4上。较早的JDK缺少内建的java.util.concurrent包,但你可以下载兼容的后续支持版本。
标准侦听器
在本文示例中,会编写一个使用ClipEven类也报告视频片断事件(如启动和暂停等)和实现ClipListener接口响应事件的视频编辑系统。
import java.util.EventListener;
class ClipEvent extends EventObject {
//...
}
public interface ClipListener
extends EventListener {
public void clipUpdate(ClipEvent e);
}
在许多程序中,事件是由一些控制类来创建,他们不仅负责维护相应的侦听器而且派发每一个事件给相应的侦听器。通常,我们可以分离这职责并且将侦听器的维护代理给一个简单的助手类dispatcher。ClipEventDispatcher示例了这种事件派发的方式:
import java.util.*;
public class ClipEventDispatcher {

final Collection listeners = new ArrayList();

public synchronized void
addListener(ClipListener l) {
listeners.add(l);
}
public synchronized void
removeListener(ClipListener l) {
listeners.remove(l);
}
public void fireEvent(ClipEvent e) {
for(Iterator i = copyListeners(); i.hasNext();) {
ClipListener l = (ClipListener) i.next();
l.clipUpdate(e);
}
}
private synchronized Iterator copyListeners() {
return new ArrayList(listeners).iterator();
}
}
ClipEventDispatcher暴露出如前面所讨论的典型的下发问题。如果任何一个ClipListener有较慢的clipUpdate方法实现就会导致其他侦听器等待。派发器的作者决定哪一个线程调用fireEvent方法,而ClipListener的开发人员却没有办法自定义事件的下发。
JAVA中灵活的任务执行:Executor接口
J2SE 5.0标准化了java.util.concurrent包,包含来自Doug Lea创建的Executor接口。Executor运行实现了java.lang.Runnable接口的任务。
class MyCoolTask implements Runnable {
public void run() {
//... do useful stuff
}
}
Thread t = new Thread(new MyCoolTask());
t.start();
在Executor使用Runnables是类似的:
Executor e = ...
e.execute(new MyCoolTask());
转递给execute方法的Runnable任务包含被Executor调用的run方法。但不像Thread只可以调用启动方法一次,Executors可以运行许多Runnable任务。不同的Executors体现不同的执行任务的策略。例如,J2SE 5.0提供一个Executor作为调度器,这意味着他可以按照配置的时间周期性地运行任务。在下一页的Useful Executors部分详细描述了几种不同的Executor,但首先我们来看一下如何用他们来解决事件派发问题。
用DispatchableEvent增加灵活性
为了使组合Executor和事件更容易,我开发了一个Dispatchable Event Library,他提供了助手类DispatchableEventSupport(用来维护侦听器和事件派发)。在内部,DispatchableEventSupport实例使用一个Executor来触发事件,因此可以改变Executor来自定义事件下发策略。
下面是一个使用DispatchableEvent类库来重写的ClipEventDispatcher示例:
import org.recoil.pixel.dispatchable.*;
import org.recoil.pixel.executor.*;
public class ClipEventDispatcher {
Executor e = new DirectExecutor(); //[1]
DispatchableEventSupport d =
new DispatchableEventSupport(e);

public void addListener(ClipListener l) {
d.addListener(l);
}

public void removeListener(ClipListener l) {
d.removeListener(l);
}

public void fireEvent(ClipEvent e) {
d.fireEvent(new DispatchableEvent
(e) {
public void
dispatch( ClipListener l, ClipEvent ce) {
l.clipUpdate(ce); //[2]
}
});
}
}
在行[1]上我们使用DirectExecutor来简化重建原始的ClipEventDispatcher行为。事件下发可以通过变化使用的Executor来自定义,或者在DispatchableEventSupport被创建时或者在侦听器增加时。
在行[2]上你只需要如此简单的代码来集成到你的应用中。Dispatchable Event Library处理了事件下发的机制,通常你所需要做的只是调用的回调函数(如clipUpdate)
Dispatchable Event Library详解
Dispatchable Event Library包含几个有用的助手类来派发任何类型的事件。关键的几个类在org.recoil.pixel.dispatchable包中:
DispatchableEventDispatcher:使用Executor触发事件,但不提供侦听器的维护。在你想为现有的事件派发代码增加灵活性是非常有用。
DispatchableEventSupport:大部分应用想要使用这个助手类,他为DispatchableEventDispatcher增加了侦听器维护。如果你了解java.beans.PropertyChangeSupport你会觉得他也很熟悉。
PropertyChangeEventDispatcher:组合了DispatchableEventDispatcher
和PropertyChangeSupport,为PropertyChangeEvents提供了灵活的派发策略。这也是一个研究如何将DispatchableEvents与现有代码集成的好例子。
DispatchableEvent:用来扩展你的事件下发代码的抽象类。
有用的Executors
Dispatchable Event Library的力量来自可以被用来自定义事件下发的可用Executors。下面我来看一下可用的Executors组:
Dispatchable Event Library包含org.recoil.pixel.executor包:
DirectExecutor:DirectExecutor在同一线程内同步调用提供给他的代码。如果和DispatchableEventSupport一起使用这个类,你可以得到通用的侦听器行为,这也是一个有用的缺省值。
AWTExecutor:AWTExecutor在AWT事件派发线程的调度代码。事件与AWTEvents交互。因此,由这个Executor调用的侦听器可以自由地调用更新AWT和Swing GUI组件的方法而不需要使用SwingUtilities.invokeLater(),因为他们已经在正确的线程中被子调用。
MIDPExecutor:MIDPExecutor在J2ME MIDlet中与AWTExecutor一致。他确保你的事件通过需要与MIDlet"s GUI交互的callSerially方法下发。
例如,为了在AWT事件派发线程中使用AWTExecutor来下发ClipEvents:
import org.recoil.pixel.dispatchable.*;
import org.recoil.pixel.executors.*;
Executor e = new AWTExecutor();
DispatchableEventSupport d =
new DispatchableEventSupport(e);
J2SE5.0内建的Executor
新的J2SE 5.0类java.util.concurrent.Executors被用来创建复杂的线程池。你可以在池中配置线程数量,设置延迟或者周期调度。
例如,使用J2SE 5.0 Executor提供一个容纳5个事件下发线程的线程池
import org.recoil.pixel.dispatchable.*;
import java.util.concurrent.*;
Executor tp = Executors.newFixedThreadPool(5);
DispatchableEventSupport d = new
DispatchableEventSupport(tp);
J2EE并没有提供标准的线程池功能,但Executor可以通过JMS或者消息BEAN来实现提供可配置的事件下发。
问题解决
现在我们已经看到Dispatchable Event Library和一些Executors,我们可以看一上如何使用这些工具来避免常见的侦听器问题。
避免等待
DispatchableEventSupport提供2个addListener方法来避免侦听器等待问题:
public void addListener(L listener);
public void addListener(L listener,
Executor executor);
addListener(L listener)方法在DispatchableEventSupport被创建的时候共享默认的Executor集合。而addListener(L listener, Executor executor)方法关联自定义的Executor。
这种方式不仅为组件的使用者提供了自定义事件下发的一种好的方式,而且帮助他们通过只有2个参数的addListener方法来分离侦听器。
import org.recoil.pixel.dispatchable.*;
public class SharedComponent {
DispatchableEventSupport d =
new DispatchableEventSupport();
public void
addListener(ClipListener l, Executor e) {
d.addListener(l, e);
}

public void fireEvent(ClipEvent e) {
d.fireEvent(new DispatchableEvent
(e) {
public void
dispatch( ClipListener l, ClipEvent ce) {
l.clipUpdate(ce);
}
});
}

[...]
}
给SharedComponent增加侦听器的开发人员被强制为每一个侦听器定义一个Executor。假设每一个开发人员保持Executor为私有的,那么他的侦听器就有一定的分离量。这在他们使用基于线程池的Executor时非常有用。
如果所有相关的代码都在团队的控制下,那么SharedComponent是足够的,但这还不能完全解决等待问题。如果你因为使用遗留的或第三方代码而必须支持低效的侦听器时,你可以通过控制和强制每一个侦听器拥有自己的Executor来增加相互的独立性。
import org.recoil.pixel.dispatchable.*;
import java.util.concurrent.*;
public class DefensiveComponent {
private final
DispatchableEventSupport d =
new DispatchableEventSupport();
public void addListener(ClipListener l) {
Executor e=Executors.newSingleThreadExecutor();
d.addListener(l, e);
}
public void removeListener(ClipListener l) {
d.removeFirstInstanceOfListener(l);
}

public void fireEvent(ClipEvent e) {
d.fireEvent(new DispatchableEvent
(e) {
public void
dispatch( ClipListener l, ClipEvent ce) {
l.clipUpdate(ce);
}
});
}

[...]
}
DefensiveComponent为每一个增加的侦听器附加对应的事件下发线程,这就分离了低效的侦听器并且确保侦听器可以被独立的处理;高效的侦听器不需要等待低效的。这种策略是简单而安全的,但也是高代价的,因为他必须创建和销毁许多线程。在大部分情况下,需要通过Executors创建合理大小的线程池也平衡独立性和代价。
同步多事件流
DispatchableEvent允许你通过一个简单的Executor多元化相应事件来同步来自不同异步资源的事件。
例如,考虑一个支持鼠标和语音识别的多模画板应用。
通常语音识别在一断语音被识别时派发一个事件。想像用户选择一个图形然后说“删除。显然我们希望鼠标事件被首先处理,否则可能会删除错误的对象。一种简单地解决这个问题的方法是使用AWTExecutor来下发语音事件,他会在事件被收到时将其放在AWT事件队列中,确保首先处理MouseEvents。
这个想法可以扩展到更多的异步事件流,通过将每一个事件源作为引用放到一个共享的基于队列的Executor中。每一个事件根据顺序放在队列中,交叉地下发。
小结
这篇文章专注于可能发生在侦听器范例中的问题。我们看到一个简单的派发类库如何通过Executors被用来自定义事件下发。使用不同的策略你可以将你的组件与子系统(如AWT)集成,你可以通过允许客户定义使用的Executor来给予他们更多的选择,或者你可以从另一方面来分离低效的侦听器来防止等待。
资源
·onjava.com:onjava.com
·Matrix-Java开发者社区:http://www.matrix.org.cn/
·The Dispatchable Event Library
Andrew Thompson拥有9年的JAVA工作经验,现在工作于Finetix LLC。

Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd
推荐阅读
  • 本文探讨了在Java多线程环境下,如何确保具有相同key值的线程能够互斥执行并按顺序输出结果。通过优化代码结构和使用线程安全的数据结构,我们解决了线程同步问题,并实现了预期的并发行为。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文将深入探讨PHP编程语言的基本概念,并解释PHP概念股的含义。通过详细解析,帮助读者理解PHP在Web开发和股票市场中的重要性。 ... [详细]
  • 装饰器是一种用于在不修改原函数代码的情况下,动态地添加功能的工具。它允许你在函数执行前后插入额外的逻辑,从而增强或改变函数的行为。 ... [详细]
  • 本文总结了Java程序设计第一周的学习内容,涵盖语言基础、编译解释过程及基本数据类型等核心知识点。 ... [详细]
  • 本文介绍如何在现有网络中部署基于Linux系统的透明防火墙(网桥模式),以实现灵活的时间段控制、流量限制等功能。通过详细的步骤和配置说明,确保内部网络的安全性和稳定性。 ... [详细]
  • 深入理解Shell脚本编程
    本文详细介绍了Shell脚本编程的基础概念、语法结构及其在操作系统中的应用。通过具体的示例代码,帮助读者掌握如何编写和执行Shell脚本。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • TechStride 网站
    TechStride 成立于2014年初,致力于互联网前沿技术、产品创意及创业内容的聚合、搜索、学习与展示。我们旨在为互联网从业者提供更高效的新技术搜索、学习、分享和产品推广平台。 ... [详细]
  • MySQL DateTime 类型数据处理及.0 尾数去除方法
    本文介绍如何在 MySQL 中处理 DateTime 类型的数据,并解决获取数据时出现的.0尾数问题。同时,探讨了不同场景下的解决方案,确保数据格式的一致性和准确性。 ... [详细]
  • Python 异步编程:ASGI 服务器与框架详解
    自 Python 3.5 引入 async/await 语法以来,异步编程迅速崛起,吸引了大量开发者的关注。本文将深入探讨 ASGI(异步服务器网关接口)及其在现代 Python Web 开发中的应用,介绍主流的 ASGI 服务器和框架。 ... [详细]
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • 不确定性|放入_华为机试题 HJ9提取不重复的整数
    不确定性|放入_华为机试题 HJ9提取不重复的整数 ... [详细]
author-avatar
最低调的鹌鹑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有