3

我总是很犹豫是否公开我的锁,公开它们。我总是试图将锁限制在我的实现中。我相信,不这样做会导致僵局。

我有以下课程:

class SomeClass {
    protected ArrayList<Listener> mListeners = new ArrayList<Listener>();

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

我会选择第二个选项。缺点是侦听器可以获取事件,即使它们已经未注册。好处是,侦听器回调正在等待的线程在想要取消注册侦听器时不会陷入死锁。我相信好处比坏处更重要,坏处可以很容易地记录下来。

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

我的问题不是您是否会选择普通的 ArrayList、LinkedList、ConcurrentLinkedQueue、CopyOnWriteArrayList、...!您是否会介意监听器是否可以在未注册时收到通知。关键是你是否会把锁打开。这是关于避免或不避免死锁。

请分享你的想法。谢谢!

4

4 回答 4

7

将 aCopyOnWriteArrayList用于您的侦听器数组。

这对于不经常更改的侦听器数组来说是完美的。当您遍历它们时,您正在遍历底层数组。使用 a CopyOnWriteArrayList,每次修改时都会复制此数组。所以迭代时不需要与它同步,因为每个底层数组都保证是静态的,即使在CopyOnWriteArrayList.

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

宣言:

private final CopyOnWriteArrayList<Listener> listeners;

事件触发:

for (Listener l: this.listeners) {
  l.event();
}
于 2011-11-24T16:09:00.187 回答
2

我会使用ConcurrentLinkedQueue<Listener>针对此类问题而设计的:在集合上同时添加、删除和迭代。

精确度:此解决方案可防止从取消注册的那一刻起就调用侦听器。该解决方案具有最好的准确性、最精细的粒度,并且可能是最不容易发生死锁的解决方案。

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

于 2011-11-24T15:43:20.307 回答
1

我想我也会选择第二个选项。就像我想你说的那样,第二个选项在通知听众时不会持有锁。因此,如果其中一个侦听器需要很长时间来完成它的工作,其他线程仍然可以调用addListenerorremoveListener方法,而无需等待锁被释放。

于 2011-11-24T15:50:48.183 回答
1

有几种可用的数据结构允许同时添加、删除和迭代

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

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

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

于 2011-11-24T16:07:21.343 回答