2011-11-24 35 views
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(); 
    } 

我会选择第二个选项。缺点是听众可以获得事件,即使他们已经没有注册。好处在于,一个侦听器calllback正在等待的线程在他想要注销一个侦听器时不会遇到死锁。我认为上涨比下跌更重要,这可以很容易地记录下来。

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

我的问题是,如果你会选择一个普通的ArrayList,LinkedList,ConcurrentLinkedQueue,CopyOnWriteArrayList,...!您是否会介意听众是否可以在未注册时收到通知。你是否会把锁打开,或者没有。这是关于避免死锁,或者不。

请分享您的想法。谢谢!

+0

使用[synchronizedList](http://docs.oracle.com/javase/6/docs/api/java/util/Collections html的#synchronizedList(java.util.List中))。对于通知,循环周围的同步块很好。 – aishwarya

+2

'CopyOnWriteArrayList'适用于遍历比更新更普遍的情况。 – artbristol

+0

请阅读我的编辑。这是关于锁,而不是关于容器。 –

回答

6

使用CopyOnWriteArrayList为你的监听器数组。

这非常适合不频繁更换的监听器阵列。当你迭代它们时,你正在迭代底层数组。使用CopyOnWriteArrayList,每次修改该数组时都会复制该数组。所以迭代时不需要与它同步,因为每个底层数组都保证是静态的,即使在CopyOnWriteArrayList之内使用它也是如此。

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

宣言:

​​

事件触发:

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

如果在侦听器被删除后永远不会再获得另一个事件调用是非常重要的,那么您需要使用第一个代码示例或者在监听器中设置一个标志来忽略事件。但是,如果你这样做,它确实使你的addListener和removeListener方法复杂化了,因为你必须检测这个方法是否在你的监听器的event()方法中被调用。如果使用'CopyOnWriteArrayList',那么迭代时就不必同步。这使您的问题无效。 –

+0

谢谢,有道理。顺便说一句,CopyOnWriteArrayList =我的第二个选项,概念明智。 –

+1

@ErickRobertson“请注意,您应该仍然同步添加和删除操作。”我原以为没有。你可以解释吗? – assylias

2

我会使用一个ConcurrentLinkedQueue<Listener>这是针对这种问题:添加,删除和迭代同时收集。

准确度:这个解决方案可以防止从解除注册的那一刻起调用监听器。这个解决方案具有最好的精确度,最好的粒度,并且可能是解决方案不太容易出现死锁。

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

+0

对于我的问题,这只是语法糖。这里的问题是您是否会通知同步锁定器外部或内部的侦听器。 –

+1

不加糖,因为在这种情况下不存在锁定:队列在添加和删除时具有原子锁定,并且迭代器保持一致。这意味着你开始迭代,添加一个元素,它将被调用,另一个被移除,它不会,所有这些都在同一个迭代周期中。这是两全其美的:确保同步,同时细化谁打电话和谁不打。 – solendil

+0

我明白了。该解决方案与我的第二个选项相同。在那里,听众可能会收到通知,但它可能已被注销。 –

1

我想我会选择第二个选项。就像我认为你说的那样,第二个选项在通知听众时并不保持锁定状态。因此,如果其中一个听众需要很长时间才能完成任务,其他线程仍然可以调用addListenerremoveListener方法,而无需等待释放该锁。

+0

因此,由监听器实现来过滤即将到来的通知? –

+1

@Japer D.所以问题是:如果一个事件导致侦听器触发,如果一个线程想要在侦听器列表中迭代时添加或删除侦听器,会发生什么?我只想说:艰难的运气。在下一次事件发生之前,对听众列表所做的更改将不会被“识别”。我的意思是,我不知道如何处理它。 – Michael

+0

不,它比这更简单。如果一个线程调用removeListener(他自己),他仍然可以得到一个通知,因为之前可能已经创建了监听器的副本,在第二个选项中。在第一个选项中,这是不可能的,但是由于您公开了该锁定,因此可能会导致死锁。 –

0

有可用的允许并发添加,删除和迭代

一个ConcurrentLinkedQueue是完全无锁和快速的添加和删除(棒材为O(n)traversel找到它),并添加,但有可能一些数据结构是其他线程的干扰(批量删除可能只对迭代器部分可见)

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

的),你可以建立在ConcurrentHashMap一个ConcurrentHashSet但这除了快速澳相同(有用的)性能(1)取出,用于添加和移除锁

相关问题