Какими способами можно создать ExecutorService в Java?

Ответ

ExecutorService в Java создается преимущественно через фабричные методы класса java.util.concurrent.Executors или путем прямой настройки ThreadPoolExecutor.

1. Создание с помощью Executors (наиболее распространено)

  • Фиксированный пул потоков (newFixedThreadPool) Создает пул с заданным фиксированным количеством потоков.

    ExecutorService executor = Executors.newFixedThreadPool(4);

    Подходит для задач с предсказуемой нагрузкой.

  • Кэширующий пул потоков (newCachedThreadPool) Создает пул, который создает новые потоки по мере необходимости, но переиспользует незанятые.

    ExecutorService executor = Executors.newCachedThreadPool();

    Подходит для множества коротких асинхронных задач. Может создавать много потоков при пиковой нагрузке.

  • Пул из одного потока (newSingleThreadExecutor) Гарантирует последовательное выполнение задач.

    ExecutorService executor = Executors.newSingleThreadExecutor();

    Подходит для задач, требующих строгого порядка или доступа к общему ресурсу без блокировок.

  • Пул с планировщиком (newScheduledThreadPool) Создает пул для выполнения отложенных или периодических задач.

    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    scheduler.schedule(() -> System.out.println("Task"), 5, TimeUnit.SECONDS);

2. Прямое создание через ThreadPoolExecutor (для тонкой настройки)

Дает полный контроль над параметрами пула.

ExecutorService customExecutor = new ThreadPoolExecutor(
    2,                      // corePoolSize: минимальное количество рабочих потоков
    10,                     // maximumPoolSize: максимальное количество потоков
    60L, TimeUnit.SECONDS,  // keepAliveTime: время простоя лишних потоков
    new LinkedBlockingQueue<>(100), // workQueue: очередь задач
    Executors.defaultThreadFactory(), // threadFactory: фабрика создания потоков
    new ThreadPoolExecutor.CallerRunsPolicy() // handler: политика при отказе
);

Важно: Завершение работы пула

После использования ExecutorService должен быть корректно остановлен.

executor.shutdown(); // Плавное завершение после выполнения всех задач
executor.shutdownNow(); // Попытка немедленной остановки всех задач
executor.awaitTermination(10, TimeUnit.SECONDS); // Ожидание завершения

Ответ 18+ 🔞

Давай разберёмся с этим вашим ExecutorService, а то я вижу, народ путается, как сука в трёх соснах. Создаётся он в основном двумя путями: либо через готовые фабрики, либо вручную, с настройками под себя, любимого.

1. Быстро и сердито — через Executors (для ленивых)

Тут всё просто, как три копейки. Открыл фабрику, выбрал шаблон — и поехали.

  • Фиксированный пул (newFixedThreadPool) Тут всё честно: сказал «четыре потока» — получил четыре потока. Ни больше, ни меньше. Как четыре лошадки в упряжке.

    ExecutorService executor = Executors.newFixedThreadPool(4);

    Идеально, когда знаешь, сколько у тебя работы и чтобы она не разбежалась, как тараканы.

  • Кэширующий пул (newCachedThreadPool) А вот это уже поинтереснее. Пул, который плодит потоки, как сумасшедший, если задача новая пришла, а все старые заняты. Но зато потом переиспользует остывшие. Экономный, блядь.

    ExecutorService executor = Executors.newCachedThreadPool();

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

  • Пул из одного (newSingleThreadExecutor) Один, но в поле воин. Все задачи будут выполняться строго по очереди, одна за другой. Никакой самодеятельности.

    ExecutorService executor = Executors.newSingleThreadExecutor();

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

  • Пул с будильником (newScheduledThreadPool) Это для тех, кто любит «сделай то через пять секунд» или «повторяй это каждую минуту, пока я не скажу стоп».

    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    scheduler.schedule(() -> System.out.println("Task"), 5, TimeUnit.SECONDS);

    Таймеры, планировщики — его вотчина.

2. Ручная настройка через ThreadPoolExecutor (для перфекционистов и параноиков)

А вот если тебе мало этих фабричных шаблонов, хочешь всё по полочкам разложить — добро пожаловать в ад настройки. Тут сам бог велел поковыряться.

ExecutorService customExecutor = new ThreadPoolExecutor(
    2,                      // corePoolSize: сколько потоков всегда наготове, даже если нихуя не делают
    10,                     // maximumPoolSize: максимум, до чего может распухнуть пул в час пик
    60L, TimeUnit.SECONDS,  // keepAliveTime: сколько лишние потоки будут маяться дурью, прежде чем сдохнуть
    new LinkedBlockingQueue<>(100), // workQueue: куда складывать задачи, если все потоки заняты. Очередь на 100 мест, не больше.
    Executors.defaultThreadFactory(), // threadFactory: кто будет рожать новые потоки. Можно свою замутить, чтоб имена красивые были.
    new ThreadPoolExecutor.CallerRunsPolicy() // handler: а вот это самое сокровенное! Что делать, если и очередь забита, и потоки все? Эта политика скажет: «А иди-ка ты, задачка, выполняйся в том потоке, который тебя сюда прислал!»
);

Вот тут-то и кроется вся магия, блядь, или, наоборот, все грабли. Неправильно настроишь — получишь либо простаивающие ресурсы, либо OutOfMemoryError, когда очередь лопнет.

Важнейшее предупреждение, ёпта!

Запомни раз и навсегда, как «Отче наш»: пул нужно закрывать! Нельзя просто взять и бросить его, как использованный презерватив. Иначе он так и будет висеть в памяти, потоки шастать, а приложение никогда не завершится нормально.

executor.shutdown(); // Вежливо просим: «Ребята, новых задач не ждите, доделайте то, что в очереди, и на выход».
executor.shutdownNow(); // Паника! «Всё, пиздец, бросайте работу где стоите и пытайтесь выйти!» (попытка, не факт что сработает).
executor.awaitTermination(10, TimeUnit.SECONDS); // Стоим и ждём, пока все потоки уйдут. Даём им 10 секунд, не уложились — похуй, идём дальше.

Вот так, коротко и без лишней воды. Главное — понимай, что выбираешь, а не тыкай пальцем в небо. И не забывай за собой прибирать, а то потом будешь гадать, почему приложение не выключается.