Безопасна ли ThreadPoolExecutor?

Обеспечивает ли ExecutorService безопасность потоков?

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

Ответ 1

Это правда, рассматриваемые классы JDK, похоже, не дают явной гарантии обеспечения потокобезопасной задачи. Однако на практике все реализации ExecutorService в библиотеке действительно являются потокобезопасными. Я думаю, что разумно зависеть от этого. Поскольку весь код, реализующий эти функции, был помещен в общественное достояние, нет абсолютно никакой мотивации для того, чтобы кто-либо полностью переписывал его по-другому.

Ответ 2

(В отличие от других ответов) документирован контракт безопасности : посмотрите в javaadocs interface (в отличие от javadoc методов). Например, в нижней части ExecutorService javadoc вы найдете:

Эффекты согласованности памяти: действия в потоке до представление задачи Runnable или Callable в ExecutorService случаются - перед любыми действиями, предпринятыми этой задачей, что в свою очередь произойдет - до получения результата через Future.get().

Этого достаточно, чтобы ответить на это:

"мне нужно синхронизировать доступ к исполнителю перед взаимодействием/отправкой задач?"

Нет, нет. Хорошо создавать и отправлять задания любым (правильно реализованным) ExecutorService без внешней синхронизации. Это одна из основных целей дизайна.

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

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

Аналогично, когда вы возвращаете результат задачи обратно через Future.get(), получающий поток будет гарантированно видеть все эффекты, созданные потоком рабочего исполнителя (как в возвращенном результате, так и в любом побочном эффекте изменяется рабочий - может быть, может быть сделана ошибка).

Этот договор также подразумевает, что для самих задач вполне подходит больше задач.

"Обеспечивает ли ExecutorService безопасность потоков?"

Теперь эта часть вопроса гораздо более общая. Например, не удалось найти какой-либо оператор договора безопасности потока о методе shutdownAndAwaitTermination - хотя я отмечаю, что образец кода в Javadoc не использует синхронизацию. (Хотя, возможно, существует скрытое предположение, что выключение инициируется тем же потоком, который создал Executor, а не, например, рабочий поток?)

BTW Я бы рекомендовал книгу "Java Concurrency In Practice" для хорошего grounder в мире параллельного программирования.

Ответ 3

Ваш вопрос довольно открытый: весь интерфейс ExecutorService гарантирует, что какой-то поток где-нибудь обработает отправленный экземпляр Runnable или Callable.

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

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

BlockingQueue<Runnable> workQ = new LinkedBlockingQueue<Runnable>();
ExecutorService execService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.SECONDS, workQ);
...
execService.submit(new Callable(...));

ИЗМЕНИТЬ

Основываясь на комментарии Брайана, и в случае, если я неправильно понял ваш вопрос: представление задач из нескольких потоков производителей в ExecutorService, как правило, будет потокобезопасным (несмотря на то, что оно явно не упоминается в интерфейсе API, насколько я могу рассказать). Любая реализация, которая не обеспечивала безопасность потоков, была бы бесполезной в многопоточной среде (поскольку несколько производителей/несколько потребителей являются довольно распространенной парадигмой), и это именно то, что ExecutorService (и остальная часть java.util.concurrent) было предназначен для.

Ответ 4

Для ThreadPoolExecutor ответ - это просто да. ExecutorService не гарантирует и не гарантирует, что все реализации являются потокобезопасными и не могут быть такими, как интерфейс. Эти типы контрактов выходят за рамки интерфейса Java. Тем не менее, ThreadPoolExecutor и то, и другое четко документировано как потокобезопасное. Более того, ThreadPoolExecutor управляет этой очередью заданий с помощью java.util.concurrent.BlockingQueue, который является интерфейсом, который требует, чтобы все реализации были потокобезопасными. Любая реализация java.util.concurrent.* BlockingQueue может быть безопасно принята как потокобезопасная. Любая нестандартная реализация может не быть, хотя это было бы совершенно глупо, если бы кто-то должен был предоставить очередь выполнения BlockingQueue, которая не была потокобезопасной.

Итак, ответ на ваш титульный вопрос явно да. Вероятно, ответ на следующий вопрос о вашем вопросе, поскольку между ними существуют некоторые расхождения.

Ответ 5

В отличие от того, что утверждает Luke Usherwood, это не подразумевает документация о том, что реализации ExecutorService гарантированы как потокобезопасные. Что касается вопроса о ThreadPoolExecutor в частности, см. Другие ответы.

Да, определяется связь между событиями и событиями, но это не означает ничего о безопасности потоков самих методов, о чем прокомментировал Miles. В ответе Люк Ушервуд утверждается, что первое достаточно для доказательства последнего, но фактический аргумент не сделан.

"Потоковая безопасность" может означать разные вещи, но вот простой встречный пример Executor (не ExecutorService, но это не имеет значения), который тривиально встречает требуемое действие-before, но не является нитью -safe из-за несинхронизированного доступа к полю count.

class CountingDirectExecutor implements Executor {

    private int count = 0;

    public int getExecutedTaskCount() {
        return count;
    }

    public void execute(Runnable command) {
        command.run();
    }
}

Отказ от ответственности: я не эксперт, и я нашел этот вопрос, потому что сам искал ответ.