1) Чаще всего для создания пула используется следующее API:
// один рабочий поток
Executors.newSingleThreadExecutor()
// 4 рабочих потока
Executors.newFixedThreadPool(4)
Под капотом этих простых методов создаётся объект класса ThreadPoolExecutor, который и реализует в себе механизм переиспользования потоков.
2) Для передачи новых задач существующим потокам ThreadPoolExecutor использует блокирующую очередь LinkedBlockingQueue, часто используемая реализация.
Задачи в очереди хранятся как Runnable:
private final BlockingQueue<Runnable> workQueue;
Логика работы:
- Взять самое первое значение, если очередь пустая приостановить текущий поток и ждать пока не появятся новые значения, используется потоками для ожидания новых задач
- Добавить новое значение без приостановок, используется для добавления новой задачи, чтобы случайно не заблокировать поток, работающий с ThreadPoolExecutor
3) Для настройки количества рабочих потоков ThreadPoolExecutor имеет corePoolSize и maximumPoolSize параметры, логика работы которых следующая:
- При запуске каждой новой задачи будут создаваться новые потоки, пока их количество не достигнет corePoolSize
- После достижения corePoolSize новые потоки будут создаваться только, если нет свободных и до тех пор пока не будет превышено значение maximumPoolSize
В случае когда corePoolSize == maximumPoolSize будет создано corePoolSize потоков.
4) ThreadPoolExecutor оборачивает каждый поток в Worker:
private final HashSet<Worker> workers = new HashSet<>();
Под капотом Worker запускает цикл ожидания новых задач от блокирующей очереди, а добавление задач происходит распространённым вызовом Executor.execute() метода.
Пишите в комментах ваше мнение и всем хорошего кода!
