1. Обзор
Spring ThreadPoolTaskExecutor – это JavaBean, который обеспечивает абстракцию вокруг java.util.concurrent.ThreadPoolExecutor экземпляр и предоставляет его как Spring org.springframework.core.task.TaskExecutor . Кроме того, он легко настраивается с помощью свойств corePoolSize, maxPoolSize, queueCapacity, allowCoreThreadTimeOut и keepAliveSeconds. В этом уроке мы рассмотрим свойства corePoolSize и maxPoolSize .
2. corePoolSize против maxPoolSize
Пользователи, новички в этой абстракции, могут легко запутаться в различиях между двумя свойствами конфигурации. Поэтому давайте рассмотрим каждый отдельно.
2.1. Размер ядра
corePoolSize – это минимальное количество работников, которые должны оставаться в живых без тайм-аута. Это настраиваемое свойство ThreadPoolTaskExecutor . Однако ThreadPoolTaskExecutor абстракция делегирует установку этого значения базовому java.util.concurrent.ThreadPoolExecutor . Чтобы уточнить, все потоки могут тайм — аут-эффективно установить значение corePoolSize равным нулю, если мы установили allowCoreThreadTimeOut в true .
2.2. maxPoolSize
Напротив, maxPoolSize определяет максимальное количество потоков, которые когда-либо могут быть созданы . Аналогично, свойство maxPoolSize ThreadPoolTaskExecutor также делегирует свое значение базовому java.util.concurrent.ThreadPoolExecutor . Чтобы уточнить, maxPoolSize зависит от queueCapacity в том, что ThreadPoolTaskExecutor создаст новый поток только в том случае, если количество элементов в его очереди превысит queueCapacity .
3. Так в чем же разница?
Разница между corePoolSize и maxPoolSize может показаться очевидной. Однако есть некоторые тонкости, касающиеся их поведения.
Когда мы отправляем новую задачу в ThreadPoolTaskExecutor, он создает новый поток, если запущено меньше, чем corePoolSize потоков, даже если в пуле есть незанятые потоки, или если запущено меньше, чем maxPoolSize потоков и очередь, определенная queueCapacity , заполнена.
Далее давайте рассмотрим некоторый код, чтобы увидеть примеры того, когда каждое свойство вступает в действие.
4. Примеры
Во-первых, предположим, что у нас есть метод , который выполняет новые потоки из ThreadPoolTaskExecutor с именем start Threads :
public void startThreads(ThreadPoolTaskExecutor taskExecutor, CountDownLatch countDownLatch, int numThreads) { for (int i = 0; i < numThreads; i++) { taskExecutor.execute(() -> { try { Thread.sleep(100L * ThreadLocalRandom.current().nextLong(1, 10)); countDownLatch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } }
Давайте проверим конфигурацию по умолчанию ThreadPoolTaskExecutor , которая определяет corePoolSize одного потока, неограниченный maxPoolSize/| и неограниченный queueCapacity . В результате мы ожидаем, что независимо от того, сколько задач мы запустим, у нас будет работать только один поток:
@Test public void whenUsingDefaults_thenSingleThread() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.afterPropertiesSet(); CountDownLatch countDownLatch = new CountDownLatch(10); this.startThreads(taskExecutor, countDownLatch, 10); while (countDownLatch.getCount() > 0) { Assert.assertEquals(1, taskExecutor.getPoolSize()); } }
Теперь давайте изменим corePoolSize максимум на пять потоков и убедимся, что он ведет себя так, как было объявлено. В результате мы ожидаем, что будет запущено пять потоков независимо от количества задач, отправленных в ThreadPoolTaskExecutor :
@Test public void whenCorePoolSizeFive_thenFiveThreads() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(5); taskExecutor.afterPropertiesSet(); CountDownLatch countDownLatch = new CountDownLatch(10); this.startThreads(taskExecutor, countDownLatch, 10); while (countDownLatch.getCount() > 0) { Assert.assertEquals(5, taskExecutor.getPoolSize()); } }
Аналогично, мы можем увеличить maxPoolSize до десяти, оставив corePoolSize на уровне пяти. В результате мы ожидаем запуска только пяти потоков. Чтобы уточнить, запускаются только пять потоков, потому что емкость очереди по-прежнему не ограничена:
@Test public void whenCorePoolSizeFiveAndMaxPoolSizeTen_thenFiveThreads() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(5); taskExecutor.setMaxPoolSize(10); taskExecutor.afterPropertiesSet(); CountDownLatch countDownLatch = new CountDownLatch(10); this.startThreads(taskExecutor, countDownLatch, 10); while (countDownLatch.getCount() > 0) { Assert.assertEquals(5, taskExecutor.getPoolSize()); } }
Далее мы повторим предыдущий тест, но увеличим емкость очереди до десяти и запустим двадцать потоков. Таким образом, теперь мы ожидаем запустить в общей сложности десять потоков:
@Test public void whenCorePoolSizeFiveAndMaxPoolSizeTenAndQueueCapacityTen_thenTenThreads() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(5); taskExecutor.setMaxPoolSize(10); taskExecutor.setQueueCapacity(10); taskExecutor.afterPropertiesSet(); CountDownLatch countDownLatch = new CountDownLatch(20); this.startThreads(taskExecutor, countDownLatch, 20); while (countDownLatch.getCount() > 0) { Assert.assertEquals(10, taskExecutor.getPoolSize()); } }
Аналогично, если бы мы установили емкость очереди на ноль и запустили только десять задач, у нас также было бы десять потоков в нашем ThreadPoolTaskExecutor .
5. Заключение
ThreadPoolTaskExecutor – это мощная абстракция вокруг java.util.concurrent.ThreadPoolExecutor , предоставляющий опции для настройки corePoolSize , maxPoolSize и queueCapacity . В этом уроке мы рассмотрели свойства corePoolSize и maxPoolSize , а также то, как maxPoolSize работает в тандеме с queueCapacity , что позволяет нам легко создавать пулы потоков для любого варианта использования.
Как всегда, вы можете найти код, доступный на Github .