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

时间:2011-11-24 15:39:02

标签: java synchronized locks

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

我有以下课程:

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

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

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

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

请分享您的想法。谢谢!

4 个答案:

答案 0 :(得分:6)

为监听器阵列使用CopyOnWriteArrayList

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

由于CopyOnWriteArrayList也是线程安全的,因此您无需同步add&amp; amp;删除操作。

声明:

private final CopyOnWriteArrayList<Listener> listeners;

事件触发器:

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

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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

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

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

您可以在ConcurrentHashMap上构建ConcurrentHashSet,但除了快速O(1)删除和用于添加和删除的锁

之外,它还具有相同的(有用的)属性