SingleThreadExecutor VS простой поток

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

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(runnable);

А также:

Thread thread = new Thread(runnable);
thread.start();

Я только прошу про одну тему.

Ответ 1

Executors # newSingleThreadExecutor() создает объект ThreadPoolExecutor под капотом,
см. код здесь: http://www.docjar.com/html/api/java/util/concurrent/Executors.java.html

  133       public static ExecutorService newSingleThreadExecutor() {
  134           return new FinalizableDelegatedExecutorService
  135               (new ThreadPoolExecutor(1, 1,
  136                                       0L, TimeUnit.MILLISECONDS,
  137                                       new LinkedBlockingQueue<Runnable>()));
  138       }

В документации ThreadPoolExecutor объясняется, в каких ситуациях это дает преимущества:

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

Если все, что вам нужно, это просто запускать один поток только время от времени (скажем, один раз в час), то с точки зрения производительности использование ThreadPoolExecutor может быть медленнее, поскольку вам нужно создать экземпляр всего механизма (пул + поток), затем выбросить его из памяти.

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

Ответ 2

Это абстракция, и они всегда "стоят":

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

Основное различие заключается в том, что служба позволяет отправлять несколько задач, тогда как поток может запускать ровно один Runnable. С другой стороны, вам нужно беспокоиться о таких вещах, как "выключение" службы.

Эмпирическое правило: аспекты производительности должны быть близки к "невежественным" здесь. Из-за этого вы предпочитаете "более абстрактное" решение для сервис-провайдеров. Потому что это позволяет отделить ваши проблемы от фактической резьбы. И что еще более важно: если вы когда-нибудь захотите использовать другой вид реализации для этой службы... остальная часть вашего кода не должна заботиться об этом.

Короче говоря: затраты на абстракции, но в этом случае вы обычно предпочитаете "более абстрактное" решение. Потому что, в конце концов, это снижает сложность вашего решения.

Ответ 3

Если вы получаете только один Runnable для выполнения, то между ними нет большой разницы.

Использование простой нити может быть немного более эффективным, так как создание ExecutorService такого как ThreadPoolExecutor имеет что-то помимо создания нового потока. Например, создавая блокирующую очередь, создавая политику, хотя эти вещи выполняются неявно.

И вы должны shutdown исполнителя после этого работоспособной была выполнена. В противном случае единственный поток в этом пуле никогда не выйдет.

Ответ 4

Основное различие заключается в политике выполнения задач.

Создавая экземпляр Thread или создавая подклассы Thread, вы в основном выполняете одну задачу.

Использование Executors.newSingleThreadExecutor(), с другой стороны, позволяет отправлять несколько задач. Поскольку эти задачи гарантированно не выполняются одновременно, это позволяет использовать следующие преимущества ограничения потока:

  • Не требуется синхронизация при доступе к объектам, которые не являются потокобезопасными
  • Эффекты памяти одной задачи гарантированно будут видны следующей задаче