J2SE5.0中用Executor灵活处理事件下发
作者:最低调的鹌鹑 | 来源:互联网 | 2024-10-19 18:57
内容:[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
推荐阅读
-
目录一、线程名称设置和获取二、线程的sleep()三、线程的interrupt四、join()五、yield()六、wait(),notify(),notifyAll( ...
[详细]
蜡笔小新 2024-11-18 20:33:30
-
关于进程的复习:#管道#数据的共享Managerdictlist#进程池#cpu个数1#retmap(func,iterable)#异步自带close和join#所有 ...
[详细]
蜡笔小新 2024-11-17 13:24:48
-
-
本文介绍了一个使用Spring框架和Quartz调度器实现每周定时调用Web服务获取数据的小项目。通过详细配置Spring XML文件,展示了如何设置定时任务以及解决可能遇到的自动注入问题。 ...
[详细]
蜡笔小新 2024-11-19 19:14:50
-
本文详细记录了腾讯ABS云平台的一次前端开发岗位面试经历,包括面试过程中遇到的JavaScript相关问题、Vue.js等框架的深入探讨以及算法挑战等内容。 ...
[详细]
蜡笔小新 2024-11-19 12:59:38
-
本文详细介绍了二叉堆的概念及其在Java中的实现方法。二叉堆是一种特殊的完全二叉树,具有堆性质,常用于实现优先队列。 ...
[详细]
蜡笔小新 2024-11-19 12:52:35
-
本文探讨了在使用JavaMail发送电子邮件时,抄送功能未能正常工作的问题,并提供了详细的代码示例和解决方法。 ...
[详细]
蜡笔小新 2024-11-19 12:12:24
-
本文探讨了在UIScrollView上嵌入Webview时遇到的一个常见问题:点击图片放大并返回后,Webview无法立即滑动。我们将分析问题原因,并提供有效的解决方案。 ...
[详细]
蜡笔小新 2024-11-18 21:13:13
-
在Java开发中,保护代码安全是一个重要的课题。由于Java字节码容易被反编译,因此使用代码混淆工具如ProGuard变得尤为重要。本文将详细介绍如何使用ProGuard进行代码混淆,以及其基本原理和常见问题。 ...
[详细]
蜡笔小新 2024-11-18 16:46:17
-
本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ...
[详细]
蜡笔小新 2024-11-18 14:59:39
-
线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ...
[详细]
蜡笔小新 2024-11-18 14:56:11
-
目录节点流、处理流读文件:BufferedReader的使用写文件:BufferedWriter的使用节点流处理流节点流和处理流的区别和联系字符流Buf ...
[详细]
蜡笔小新 2024-11-18 14:47:25
-
Redis 是一个高性能的开源键值存储系统,支持多种数据结构。本文将详细介绍 Redis 中的六种底层数据结构及其在对象系统中的应用,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。通过12张图解,帮助读者全面理解 Redis 的数据结构和对象系统。 ...
[详细]
蜡笔小新 2024-11-16 17:48:35
-
原文地址:https:blog.csdn.netqq_35361471articledetails84715491原文地址:https:blog.cs ...
[详细]
蜡笔小新 2024-11-19 19:22:47
-
首部|接口类型_OSI 7层模型 & TCP/IP协议首部封装格式解析 ...
[详细]
蜡笔小新 2024-11-17 18:56:46
-
1、基本使用方法异步下载并缓存-(void)sd_setImageWithURL:(nullableNSURL*)urlNS_REFINED_FOR_SWIFT;使用占位图片& ...
[详细]
蜡笔小新 2024-11-17 14:40:33
-