java.util.concurrent: карта инструментов
Исполнение задач
| Нужда | Инструмент |
|---|---|
| ограниченный пул CPU-задач | ThreadPoolExecutor / fixed pool |
| задачи по расписанию | ScheduledExecutorService |
| рекурсивное CPU-разбиение | ForkJoinPool |
| результат одной задачи | Объект Future |
| цепочка и объединение результатов | CompletableFuture |
Executor нужно завершать:
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
List<Runnable> notStarted = executor.shutdownNow();
}shutdownNow() пытается interrupt-нуть выполняющиеся задачи, но не гарантирует их остановку.
Защита состояния
Lock,ReadWriteLock,StampedLock;Atomic*,LongAdder;ConcurrentHashMap,ConcurrentLinkedQueue;CopyOnWriteArrayListдля редких записей.
Передача и координация
BlockingQueue— producer/consumer и backpressure;CountDownLatch,CyclicBarrier,Phaser— Синхронизаторы;Semaphore— лимит параллелизма;Condition— несколько wait set у одногоLock;Exchanger— парный обмен.
Как выбрать пул
Для CPU-bound работы число активных потоков обычно близко к числу ядер. Для блокирующей нагрузки допустимо больше, но предел задают память, очередь, connection pool и внешний сервис. Неограниченная очередь скрывает перегрузку ростом latency; bounded queue требует явной rejection policy.
ThreadPoolExecutor pool = new ThreadPoolExecutor(
8, 8,
0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(500),
new ThreadPoolExecutor.CallerRunsPolicy()
);CallerRunsPolicy замедляет производителя, но ее влияние на request thread нужно оценивать.
На собеседовании
- Разница execute и submit?
submitвозвращает Future и захватывает ошибку задачи;execute— нет. - Что происходит после shutdown? Новые задачи отвергаются, принятые выполняются.
- Почему bounded queue важна? Ограничивает память и делает перегрузку наблюдаемой.
- Чем LongAdder отличается от AtomicLong? Лучше масштабирует конкурентные прибавления, но
sum()не является атомарным snapshot относительно обновлений. - Гарантирует ли shutdownNow остановку? Нет.