Baeldung 翻译系列之Java并发基础:
Java中的concurrent包
Java中的Synchronized关键字
Future介绍
ThreadLocal介绍
Java线程的生命周期
如何杀掉一个Java线程
Java中的线程池介绍
实现Runnable接口还是继承Thread类
Java中的wait和notify方法
Runnable vs Callable
wait和sleep的区别
Thread.join方法介绍
Java中使用锁对象
ThreadPoolTaskExecutor中的corePoolSize和maxPoolSize
Java中的异步编程
1. 概述
Spring的ThreadPoolTaskExecutor是一个JavaBean,它提供了对java.util.concurrent.ThreadPoolExecutor实例的抽象,并将其作为Spring的org.springframework.core.task.TaskExecutor暴露出来。此外,通过corePoolSize、maxPoolSize、queueCapacity、allowCoreThreadTimeOut和keepAliveSeconds等属性,它具有高度可配置性。在本教程中,我们将重点关注corePoolSize和maxPoolSize属性。
2. corePoolSize vs. maxPoolSize
对于这个抽象的新用户可能会对这两个配置属性的区别感到困惑。因此,让我们分别看一下它们。
2.1. corePoolSize
corePoolSize是保持活动状态的最小工作线程数。它是ThreadPoolTaskExecutor的可配置属性。然而,ThreadPoolTaskExecutor的抽象将设置此值的工作委托给底层的java.util.concurrent.ThreadPoolExecutor。需要注意的是,如果我们将allowCoreThreadTimeOut设置为true,所有线程都可能超时,从而将corePoolSize的值设置为零。
2.2. maxPoolSize
相比之下,maxPoolSize定义了可以创建的最大线程数。同样,ThreadPoolTaskExecutor的maxPoolSize属性也将其值委托给底层的java.util.concurrent.ThreadPoolExecutor。需要注意的是,maxPoolSize依赖于queueCapacity,只有当队列中的项目数超过queueCapacity时,ThreadPoolTaskExecutor才会创建新线程。
3. 那么它们的区别是什么?
corePoolSize和maxPoolSize之间的区别似乎很明显。然而,它们的行为有一些微妙之处。
当我们向ThreadPoolTaskExecutor提交一个新任务时,如果运行的线程数少于corePoolSize,它将创建一个新线程,即使池中有空闲线程;或者如果运行的线程数少于maxPoolSize,并且由queueCapacity定义的队列已满,它也会创建一个新线程。
接下来,让我们看一些代码示例,了解每个属性何时起作用。
4. 示例
首先,假设我们有一个从ThreadPoolTaskExecutor执行新线程的方法,名为startThreads:
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的默认配置,它定义了一个核心线程数为一个线程,无界的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保持为五个。因此,我们只希望启动五个线程。需要注意的是,仅当queueCapacity仍然是无界的时候,才会启动五个线程:
@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());
}
}
此外,我们现在将queueCapacity增加到十个,并启动二十个线程。因此,我们现在期望总共启动十个线程:
@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());
}
}
同样地,如果我们将queueCapacity设置为零,并且只启动了十个任务,那么ThreadPoolTaskExecutor中将有十个线程。
5. 结论
ThreadPoolTaskExecutor是围绕java.util.concurrent.ThreadPoolExecutor的强大抽象,提供了配置corePoolSize、maxPoolSize和queueCapacity的选项。在本教程中,我们看了corePoolSize和maxPoolSize属性,以及maxPoolSize如何与queueCapacity配合工作,让我们能够轻松地创建适用于任何用例的线程池。
评论区