4

在实现线程安全的侦听器时,我通常想知道哪种类型的Collection侦听器最好。到目前为止,我已经找到了三个选项。

该标准Observable使用synchronized访问一个简单的ArrayList. 使用监听器的副本是可选的,但据我所知,这是一个好主意,因为它可以防止诸如

  • 侦听器在回调中删除自身(ConcurrentModificationException- 可以通过索引for循环以相反的顺序进行迭代以防止这种情况发生)
  • 在同步块内执行外部代码会阻塞整个事情。

不幸的是,实现不止几行。Collections.synchronizedList()不需要synchronizedremoveListener但这并不值得。

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();
        }
    }
}

但是其中有些Collectionsjava.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所示:

视图的迭代器是一个“弱一致”的迭代器,它永远不会抛出 ConcurrentModificationException,并保证在构造迭代器时遍历元素,并且可以(但不保证)反映构造后的任何修改。

这意味着迭代至少包括在开始迭代时注册的每个监听器,甚至可能包括在迭代期间添加的新监听器。不确定已删除的侦听器。我确实喜欢这个版本,因为它不是复制的,而且实现起来像CopyOnWriteArrayList.

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的使用方法?

4

2 回答 2

2

自 Java 1.1 以来,有一种模式优于所有这些变体。查看AWTEventMulticaster课程及其工作原理。它通过不变性提供线程安全,因此没有额外的开销。当没有或只有一个听众时,它甚至提供了处理这种情况的最有效方法。好吧,我认为它总体上提供了最有效的事件传递。

请参阅http://docs.oracle.com/javase/7/docs/api/java/awt/AWTEventMulticaster.html

如果您想知道如何为自己的事件类型实现这种模式,请查看此类的源代码。

遗憾的是 Swing 开发人员不知道这一点,并创建了可怕的 EventListenerList 导致开发人员走上了错误的道路。

顺便说一句,这种模式还解决了在事件传递期间添加的侦听器不应该看到添加之前发生的当前传递的事件的问题。免费。从 Java 1.1 开始

于 2013-09-23T17:12:35.660 回答
2

基于 ConcurrentHashMap 的实现是一个好主意还是我在这里忽略了一些东西?还是有更好的收藏品可以使用?

ConcurrentHashMap在这种情况下似乎没问题。它肯定比使用Collections.synchronizedList(). 如果在迭代器遍历地图时添加了新条目,CHM 迭代器可能会也可能不会看到地图中的新条目。如果它们在迭代期间被删除,它也可能会或可能不会看到旧条目。这两者都是由于关于何时添加/删除相关节点以及迭代器定位的竞争条件。但是迭代器总是会看到它开始时地图中的项目,只要它们没有被删除。

于 2013-09-23T18:11:12.073 回答