在实现线程安全的侦听器时,我通常想知道哪种类型的Collection
最适合保存侦听器。到目前为止,我找到了三种选择。
标准Observable
使用synchronized
访问简单ArrayList
。使用听众的副本是可选的,但据我可以说好主意,因为它可以防止像
ConcurrentModificationException
- 可以通过索引for
循环以相反顺序迭代以防止这种情况发生。遗憾的是,实施不止一条线。 Collections.synchronizedList()
synchronized
中不需要removeListener
,但这并不值得。
class ObservableList {
private final List<Listener> listeners = new ArrayList<Listener>();
public void addListener(Listener listener) {
synchronized (listeners) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
}
public void removeListener(Listener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
protected void notifyChange() {
Listener[] copyOfListeners;
synchronized (listeners) {
copyOfListeners = listeners.toArray(new Listener[listeners.size()]);
}
// notify w/o synchronization
for (Listener listener : copyOfListeners) {
listener.onChange();
}
}
}
但是Collection
中的java.util.concurrent
本身就是线程安全的并且可能更有效,因为我认为它们的内部锁定机制比简单的synchronized
块更优化。为每个通知创建副本也非常昂贵。
基于CopyOnWriteArrayList
class ObservableCopyOnWrite {
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
public void addListener(Listener listener) {
listeners.addIfAbsent(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
protected void notifyChange() {
for (Listener listener : listeners) {
listener.onChange();
}
}
}
应该大致完成第一个版本的功能,但副本更少。添加/删除侦听器并不是一个非常频繁的操作,这意味着副本也不应该非常频繁。
我通常使用的版本基于ConcurrentHashMap
,其中.keySet()
表示Set
,此处用作CopyOnWriteArrayList
:
视图的迭代器是一个“弱一致”的迭代器,它永远不会抛出ConcurrentModificationException,并保证遍历构造迭代器时存在的元素,并且可能(但不保证)反映构造之后的任何修改。 / p>
这意味着迭代至少包括在开始迭代时注册的每个侦听器,甚至可能包括在迭代期间添加的新侦听器。关于被删除的侦听器不确定。我喜欢这个版本,因为它不是复制,而是像class ObservableConcurrentSet {
private final Set<Listener> listeners = Collections.newSetFromMap(new ConcurrentHashMap<Listener, Boolean>());
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
protected void notifyChange() {
for (Listener listener : listeners) {
listener.onChange();
}
}
}
一样简单实现。
ConcurrentHashMap
基于Collection
的实施是一个好主意还是我在这里忽略了什么?或者是否有更好的{{1}}可供使用?
答案 0 :(得分:2)
自Java 1.1以来,有一种模式优于所有这些变体。查看AWTEventMulticaster
类及其工作原理。它通过不变性提供线程安全性,因此没有额外的开销。当没有或只有一个监听器时,它甚至提供了处理案例的最有效方法。好吧,我认为它提供了最有效的事件传递。
请参阅http://docs.oracle.com/javase/7/docs/api/java/awt/AWTEventMulticaster.html
如果您想知道如何为自己的事件类型实现此类模式,请查看此类的源代码。
很遗憾,Swing开发人员并不知道这一点,并且创建了可怕的EventListenerList,导致开发人员走错路。
顺便说一下,这种模式还解决了在事件传递过程中侦听器添加的问题,不应该看到在添加之前发生的当前传递的事件。免费。从Java 1.1开始
答案 1 :(得分:2)
在这种情况下,基于ConcurrentHashMap的实现是一个好主意还是我在这里忽略了什么?或者是否有更好的收藏品使用?
ConcurrentHashMap
似乎没问题。它肯定比使用Collections.synchronizedList()
更好。如果在迭代器行走地图时添加了CHM迭代器,则它们可能会也可能不会在地图中看到新条目。如果在迭代期间删除旧条目,它也可能会或可能不会看到旧条目。这两种情况都是由于竞争条件导致添加/删除有问题的节点以及迭代器的位置。但是只要它们没有被删除,迭代器就会始终看到地图中的项目。