我应该同步监听器通知吗?

我总是非常犹豫要把我的锁公开,让它们公开。 我总是试图将锁限制在我的实现中。 我相信,不这样做是一个僵局的秘诀。

我有以下课程:

class SomeClass { protected ArrayList mListeners = new ArrayList(); protected void addListener(Listener listener) { synchronized (mListeners) { mListeners.add(listener); } } protected void removeListener(Listener listener) { synchronized (mListeners) { mListeners.remove(listener); } } ... } 

当SomeClass想要通知他的听众时,你会这样做:

  synchronized (mListeners) { for (Listener l : mListeners) { l.event(); } } 

要么

  Listener[] listeners = null; synchronized (mListeners) { listeners = mListeners.toArray(); } for (Listener l : listeners) { l.event(); } 

我会选择第二种选择。 缺点是听众可以获得活动,即使他们已经取消注册。 好处是,一个侦听器calllback正在等待的线程,当他想取消注册一个监听器时,它不会遇到死锁。 我认为好处比下行更重要,可以很容易地记录下来。

所以这里的问题基本上是:你会暴露你的锁吗?

我的问题不是你选择一个简单的ArrayList,LinkedList,ConcurrentLinkedQueue,CopyOnWriteArrayList,……! 您是否会介意侦听器是否可以在未注册时收到通知。 无论你是否将锁打开,或不是。 这是关于避免死锁。

请分享你的想法。 谢谢!

CopyOnWriteArrayList用于侦听器数组。

这对于不经常更改的侦听器arrays非常适合。 当您遍历它们时,您将遍历底层数组。 使用CopyOnWriteArrayList ,每次修改此数组时都会复制该数组。 所以在迭代时不需要与它同步,因为每个底层数组都保证是静态的,甚至超过它在CopyOnWriteArrayList使用。

由于CopyOnWriteArrayList也是线程安全的,因此您无需同步添加和删除操作。

宣言:

 private final CopyOnWriteArrayList listeners; 

事件触发器:

 for (Listener l: this.listeners) { l.event(); } 

我会使用ConcurrentLinkedQueue来解决这类问题:在集合上同时添加,删除和迭代。

精度:此解决方案可防止在取消注册时调用侦听器。 此解决方案具有最佳的准确性,最精细的粒度,并且可能是解决方案,更容易出现死锁。

如果你坚持你的两个提议,我会选择第一个,因为它更安全,但它可能会引发更长的锁并降低整体性能(这取决于你添加或删除监听器的频率)。 第二种解决方案被打破了,因为当听众注销时,很可能是因为他无法处理事件。 在这种情况下,调用它将是一个非常糟糕的主意,并且无论如何它将违反听众合同。

我想我也会选择第二个选项。 就像我想你说的那样,第二个选项在通知听众时没有锁定。 因此,如果其中一个侦听器需要很长时间来执行其操作,则其他线程仍可以调用addListenerremoveListener方法,而无需等待释放锁。

有几种可用的数据结构允许并发添加,删除和迭代

一个ConcurrentLinkedQueue是完全无锁的,快速添加和删除(禁止O(n)遍历以找到它)并添加,但可能存在一些其他线程的干扰(批量删除可能只对迭代器部分可见)

copyOnWrite 列表和set在添加和删除时较慢,因为它们需要数组分配和复制,但迭代完全没有干扰,并且与遍历相同大小的ArrayList一样快(迭代发生在集合的快照上)

您可以在ConcurrentHashMap上构建ConcurrentHashSet,但除了快速O(1)删除和用于添加和删除的锁之外,它具有相同的(有用的)属性