在实现线程安全的监听器时,我通常想知道哪种类型的 Collection
最适合容纳监听器。到目前为止,我找到了三个选项。
标准的Observable
使用同步
访问一个简单的ArrayList
。使用监听器的副本是可选的,但据我所知,这是一个好主意,因为它可以防止像
- 监听器在回调中删除自身(
ConcurrentModificationException
- 可以通过索引for
循环以相反的顺序迭代以防止发生这种情况) - 在同步块(synchronized block)内执行外部代码可以阻止整个事情。
不幸的是,实现不止几行。 Collections.synchronizedList()
不需要 removeListener
中的 synchronized
但这并不值得。
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();
}
}
}
但是 java.util.concurrent
中的 Collection
本质上是线程安全的并且可能更高效,因为我认为它们的内部锁定机制得到了更好的优化而不是简单的 synchronized
block 。为每个通知创建一个副本也非常昂贵。
基于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
:
The view's iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException, and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.
这意味着迭代至少包括在开始迭代时注册的每个监听器,甚至可能包括在迭代期间添加的新监听器。不确定已删除的听众。我很喜欢这个版本,因为它没有复制,而且与 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
可供使用?
最佳答案
Is a ConcurrentHashMap based implementation a good idea or did I overlook something here? Or is there an even better Collection to use?
ConcurrentHashMap
在这种情况下似乎没问题。它肯定比使用 Collections.synchronizedList()
更好。如果在迭代器遍历 map 时添加新条目,CHM 迭代器可能会也可能不会看到 map 中的新条目。如果它们在迭代期间被删除,它也可能会或可能不会看到旧条目。这两者都是由于关于何时添加/删除有问题的节点以及迭代器所在位置的竞争条件。但是迭代器总是会看到 map 中的项目,只要它们没有被删除。
关于java - Listener/Observable 实现,同步与并发集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18964661/