簡體   English   中英

Listener / Observable實現,同步和並發集合

[英]Listener / Observable implementation, synchronized vs concurrent collections

在實現線程安全的偵聽器時,我通常想知道哪種類型的Collection最適合保存偵聽器。 到目前為止我找到了三個選項。

標准Observable使用對簡單ArrayList synchronized訪問。 使用聽眾的副本是可選的,但據我所知,因為它可以防止類似的問題

  • 監聽器在回調中自行刪除( ConcurrentModificationException - 可以通過索引的for循環以相反的順序迭代以防止這種情況)
  • 在synchronized塊中執行外部代碼可以阻止整個事務。

不幸的是,實施不止一條線。 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.concurrentCollection本身就是線程安全的並且可能更有效,因為我認為它們的內部鎖定機制比簡單的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM