2011-11-24 85 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)取出,用於添加和移除鎖

相關問題