Listener / Observable实现,同步和并发集合

时间:2013-09-23 16:49:14

标签: java concurrency

在实现线程安全的侦听器时,我通常想知道哪种类型的Collection最适合保存侦听器。到目前为止,我找到了三种选择。

标准Observable使用synchronized访问简单ArrayList。使用听众的副本是可选的,但据我可以说好主意,因为它可以防止像

这样的问题
  • 侦听器在回调中移除自身(ConcurrentModificationException - 可以通过索引for循环以相反顺序迭代以防止这种情况发生。
  • 在synchronized块中执行外部代码可以阻止整个事情。

遗憾的是,实施不止一条线。 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}}可供使用?

2 个答案:

答案 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迭代器,则它们可能会也可能不会在地图中看到新条目。如果在迭代期间删除旧条目,它也可能会或可能不会看到旧条目。这两种情况都是由于竞争条件导致添加/删除有问题的节点以及迭代器的位置。但是只要它们没有被删除,迭代器就会始终看到地图中的项目。