[英]Collections.SynchronizedList implementation - synchronized method vs mutex
[英]Listener / Observable implementation, synchronized vs concurrent collections
在實現線程安全的偵聽器時,我通常想知道哪種類型的Collection
最適合保存偵聽器。 到目前為止我找到了三個選項。
標准Observable
使用對簡單ArrayList
synchronized
訪問。 使用聽眾的副本是可選的,但據我所知,因為它可以防止類似的問題
ConcurrentModificationException
- 可以通過索引的for
循環以相反的順序迭代以防止這種情況) 不幸的是,實施不止一條線。 Collections.synchronizedList()
不需要在removeListener
synchronized
,但這不值得。
class ObservableList {
private final List<Listener> listeners = new ArrayList<Listener>();
public void addListener(Listener listener) {
synchronized (listeners) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
}
public void removeListener(Listener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
protected void notifyChange() {
Listener[] copyOfListeners;
synchronized (listeners) {
copyOfListeners = listeners.toArray(new Listener[listeners.size()]);
}
// notify w/o synchronization
for (Listener listener : copyOfListeners) {
listener.onChange();
}
}
}
但是java.util.concurrent
有Collection
本身就是線程安全的並且可能更有效,因為我認為它們的內部鎖定機制比簡單的synchronized
塊更優化。 為每個通知創建副本也非常昂貴。
基於CopyOnWriteArrayList
class ObservableCopyOnWrite {
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
public void addListener(Listener listener) {
listeners.addIfAbsent(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
protected void notifyChange() {
for (Listener listener : listeners) {
listener.onChange();
}
}
}
應該大致做第一個版本,但副本更少。 添加/刪除偵聽器不是一個非常頻繁的操作,這意味着副本也不應該非常頻繁。
我通常使用的版本基於ConcurrentHashMap
,它聲明了.keySet()
,在這里用作Set
:
視圖的迭代器是一個“弱一致”的迭代器,它永遠不會拋出ConcurrentModificationException,並保證遍歷構造迭代器時存在的元素,並且可能(但不保證)反映構造之后的任何修改。
這意味着迭代至少包括在開始迭代時注冊的每個偵聽器,甚至可能包括在迭代期間添加的新偵聽器。 關於被刪除的偵聽器不確定。 我喜歡這個版本,因為它不是復制,而且像CopyOnWriteArrayList
一樣簡單。
class ObservableConcurrentSet {
private final Set<Listener> listeners = Collections.newSetFromMap(new ConcurrentHashMap<Listener, Boolean>());
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
protected void notifyChange() {
for (Listener listener : listeners) {
listener.onChange();
}
}
}
基於ConcurrentHashMap
的實現是一個好主意還是我在這里忽略了什么? 或者是否有更好的Collection
?
自Java 1.1以來,有一種模式優於所有這些變體。 查看AWTEventMulticaster
類及其工作原理。 它通過不變性提供線程安全性,因此沒有額外的開銷。 當沒有或只有一個監聽器時,它甚至提供了處理案例的最有效方法。 嗯,我認為它提供了最有效的事件傳遞。
見http://docs.oracle.com/javase/7/docs/api/java/awt/AWTEventMulticaster.html
如果您想知道如何為自己的事件類型實現此類模式,請查看此類的源代碼。
遺憾的是,Swing開發人員並不知道這一點,並創建了可怕的EventListenerList,導致開發人員走錯路。
順便說一下,這種模式也解決了在事件傳遞過程中監聽器添加的問題,不應該看到在添加之前發生的當前傳遞的事件。 免費。 自Java 1.1以來
基於ConcurrentHashMap的實現是一個好主意還是我在這里忽略了什么? 或者是否有更好的收藏品?
在這種情況下, ConcurrentHashMap
似乎沒問題。 它肯定比使用Collections.synchronizedList()
更好。 如果在迭代器行走地圖時添加了CHM迭代器,則它們可能會也可能不會在地圖中看到新條目。 如果在迭代期間刪除舊條目,它也可能會或可能不會看到舊條目。 這兩種情況都是由於競爭條件導致添加/刪除有問題的節點以及迭代器的位置。 但是只要沒有刪除它,迭代器就會始終看到地圖中的項目。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.