Должен ли я синхронизировать уведомления слушателя или нет? - PullRequest
3 голосов
/ 24 ноября 2011

Я всегда очень не решаюсь раскрыть свои замки, сделать их публичными.Я всегда стараюсь ограничивать блокировки моей реализацией.Я полагаю, что не делать этого - это рецепт тупиков.

У меня есть следующий класс:

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

Я бы выбрал второй вариант.Недостатком является то, что слушатели могут получать события, даже если они уже зарегистрированы.Положительным моментом является то, что поток, на котором ожидает ответный вызов слушателя, не может зайти в тупик, когда он хочет отменить регистрацию слушателя.Я считаю, что преимущество гораздо важнее, чем недостаток, который легко документировать.

Таким образом, вопрос здесь в основном: вы выставите свой замок или нет?

Мойвопрос НЕ в том случае, если вы выбрали бы простой ArrayList, LinkedList, ConcurrentLinkedQueue, CopyOnWriteArrayList, a ...!Не возражаете ли вы, если слушатель может получить уведомление, пока оно уже не зарегистрировано.Будет ли вы открыть замок или нет.Речь идет о том, чтобы избежать тупиков или нет.

Пожалуйста, поделитесь своими мыслями.Спасибо!

Ответы [ 4 ]

6 голосов
/ 24 ноября 2011

Используйте CopyOnWriteArrayList для ваших массивов слушателей.

Это идеально подходит для массивов слушателей, которые меняются нечасто.Когда вы перебираете их, вы перебираете базовый массив.С CopyOnWriteArrayList этот массив копируется каждый раз, когда он изменяется.Таким образом, нет необходимости синхронизироваться с ним при итерации, поскольку каждый базовый массив гарантированно является статическим, даже после его использования в CopyOnWriteArrayList.

Поскольку CopyOnWriteArrayList также является поточно-ориентированным, вам не нужнодля синхронизации операций добавления и удаления.

Объявление:

private final CopyOnWriteArrayList<Listener> listeners;

Триггер события:

for (Listener l: this.listeners) {
  l.event();
}
2 голосов
/ 24 ноября 2011

Я бы использовал ConcurrentLinkedQueue<Listener>, который создан для такого рода проблем: добавление, удаление и повторение одновременно в коллекции.

Точность: это решение предотвращает вызов слушателя с момента его отмены регистрации. Это решение имеет наилучшую точность, лучшую гранулярность и, вероятно, является менее подверженным тупику.

Если вы непреклонны в своих двух предложениях, я бы выбрал первое, потому что оно более безопасное, но оно может спровоцировать более длительные блокировки и снизить общую производительность (в зависимости от того, как часто вы добавляете или удаляете слушателей). Второе решение не работает, потому что когда слушатель отменяет регистрацию, это может быть потому, что он не может обрабатывать события. В таком случае называть это было бы очень плохой идеей, и в любом случае это было бы нарушением контракта слушателя.

1 голос
/ 24 ноября 2011

Полагаю, я бы тоже выбрал второй вариант.Как я думаю, вы сказали, что второй вариант не удерживает блокировку, когда уведомляет слушателей.Таким образом, если одному из слушателей потребуется много времени, другие потоки все равно могут вызывать методы addListener или removeListener, не дожидаясь снятия блокировки.

0 голосов
/ 24 ноября 2011

есть несколько доступных структур данных, которые позволяют одновременное добавление, удаление и повторение

a ConcurrentLinkedQueue полностью блокируется и быстро добавляется и удаляется (за исключением обходного пути O (n), чтобы найти его)) и добавить, но могут быть некоторые помехи другим потокам (массовое удаление может быть видимо только для итератора)

copyOnWrite list и set медленнее при добавлении и удалении, потому что они влекут за собой выделение и копирование массива, однако итерация полностью свободна от помех и так же быстро, как и обход ArrayList того же размера (итерация происходит на снимке набора)

вы можете создать ConcurrentHashSet на ConcurrentHashMap , но он имеет те же (полезные) свойства, что и быстрое удаление O (1) и блокировки, используемые для добавления и удаления

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...