Thread Safe Publisher

1

Я столкнулся с проблемой параллелизма Java, которую, как я думал, решил, но теперь я чувствую, что у моего решения есть проблема.

Проблема:

Внедрите недостающие методы в потоковом безопасном и эффективном образом. Система должна подписаться только на первый запрос на T key и должна отказаться от подписки, если больше не осталось слушателей для данного ключа.

interface Listener {
    void onData();
}

abstract class Publisher<T> {       
    public void subscribe(T key, Listener l) {
        // TODO complete
    }

    public void unsubscribe(T key, Listener l) {
        // TODO complete
    }

    public void publish(T key) {
        // TODO complete
    }

    public abstract void reallyLongSubscribeRequest(T key);
    public abstract void reallyLongUnsubscribeRequest(T key);
}

Мое решение состояло в том, чтобы сохранить ключ и слушателей в ConcurrentHashMap и использовать исполнитель пула потоков для запуска вызова очень длинных подписных и неподданных методов:

abstract class Publisher<T> {
    private final int POOL_SIZE = 5;

    private final ConcurrentHashMap<T, List<Listener>> listeners = new ConcurrentHashMap<>();
    private final ScheduledExecutorService stpe = Executors.newScheduledThreadPool(POOL_SIZE);

    public void subscribe(T key, Listener l) {
        if (listeners.containsKey(key)) {
            listeners.get(key).add(l);
        } else {
            final T keyToAdd = key;
            final List<Listener> list = new LinkedList<Listener>();
            list.add(l);

            Runnable r = new Runnable() {
                public void run() {
                    reallyLongSubscribeRequest(keyToAdd);
                    listeners.putIfAbsent(keyToAdd, list);
                }
            };

            stpe.execute(r);
        }
    }

    public void unsubscribe(T key, Listener l) {
        if (listeners.containsKey(key)) {
            List<Listener> list = listeners.get(key);
            list.remove(l);

            if (list.size() == 0) {
                final T keyToRemove = key;
                Runnable r = new Runnable() {
                    public void run() {
                        reallyLongUnsubscribeRequest(keyToRemove);
                    }
                };
                stpe.execute(r);
            }
        }
    }

    public void publish(T key) {
        if (listeners.containsKey(key)) {
            final List<Listener> list = listeners.get(key);
            for (Listener l : list) {
                l.onData();
            }
        }
    }

    public abstract void reallyLongSubscribeRequest(T key);
    public abstract void reallyLongUnsubscribeRequest(T key);
}

Я теперь обеспокоен тем, что это уже не потокобезопасно, потому что

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

  2. Как и в # 1, в unssubscribe, что если поток обменивается между вводом истинной ветки вложенного if и выполнения Runnable?

Поэтому мои вопросы

  1. Являются ли мои вышеуказанные проблемы действительными или я слишком усложняю вопрос (или я неправильно понял, как работает сокращение времени)?
  2. Если да, может ли это быть легко исправлено или там намного лучше/проще/проще?
Теги:
multithreading
concurrency

2 ответа

1
Лучший ответ

У вас две проблемы:

  • Ваши методы subscribe, unsubscribe и publish должны быть synchronized чтобы сделать их потокобезопасными.

  • У вас должен быть только один поток, чтобы выполнять reallyLong...() которые ждут в Queue. Вы отправляете в Queue сообщение, говорящее о том, чтобы делать то или другое, и это так. Очередь гарантирует, что они происходят один за другим.

У вас также есть ошибка в коде. Вы действительно выполняете reallyLongSubscribeRequest(...) когда ключ не существует на карте, но вы не удаляете ключ с карты при удалении последнего прослушивателя.

  • 0
    Разве это не заблокирует поток до reallyLong...() пор, пока не reallyLong...() , что reallyLong...() снижению производительности?
  • 0
    Я имею в виду один дополнительный поток, подожди, я сделаю небольшой пример.
Показать ещё 4 комментария
1

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню