Spring中的线程池与任务调度

线程池已经成为 Java 开发中必不可少的一个组件了,在使用 Spring 时,不需要自己重头去使用线程池。

Spring 已经提供了非常完备的封装,可以直接使用 Spring 提供的接口。

本文基于 Spring5.3 和 OpenJDK11

1. Spring 中的任务

Spring 中对与任务的执行提供了两种抽象, TaskExecutorTaskScheduler,分别表示执行异步任务和定时任务。

Executor 在 JDK 中是线程池的名称。一个 executor 用来表示执行任务的线程池,其中最少会有一个线程,每个线程都可以用来执行同步或者异步任务。

Scheduler 表示的是定时任务,定时任务的触发,支持 JDK 中的 TimerQuartz Scheduler

2. TaskExecutor

TaskExecutor 接口继承了 JDK 中的 Executor。在 JDK 中,ThreadPoolExecutor 继承了 Executor,也是一个很常用的接口。

Spring 对这些实现屏蔽了细节,无论是开发 Java EE 应用还是 Java SE 应用,都可以直接使用 TaskExecutor。

TaskExecutor 的实现

Spring 中已经实现了多种类型的 TaskExecutor,在绝大多数情况下,不需要自己去实现。

  • SyncTaskExecutor:用来执行非异步的任务,通常用于不需要多线程的场景,实际用的比较少,通常用来执行测试用例
  • SimpleAsyncTaskExecutor:这个实现不会重用任何的线程,每当有新任务的时候,都是重新创建一个线程
  • ConcurrentTaskExecutor:这个实现是对 Executor 的适配,可以配置 Executor 的全部参数,但是一般很少使用,除非需要完全自主配置线程池
  • ThreadPoolTaskExecutor:这个实现最常用,其中封装了 ThreadPoolExecutor,如果还需要使用 Executor 的其他实现,可以使用 ConcurrentTaskExecutor
  • WorkManagerTaskExecutor:这个用的就更少了,这个实现封装了 WebLogic 的 API,以便在 WebLogic 中间件上运行 Spring 程序
  • DefaultManagedTaskExecutor:这个实现的目标是替代 WorkManagerTaskExecutor。

TaskExecutor 的使用

下面以最常见的 ThreadPoolTaskExecutor 为例来演示 TaskExecutor 的使用。

创建一个待执行的任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TaskDemo implements Runnable{

private String message;

public TaskDemo(String message) {
this.message = message;
}

@Override
public void run() {
System.out.println(message);
}
}

再创建一个执行任务的执行器:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SpringTaskDemo {
private ThreadPoolTaskExecutor threadPoolTaskExecutor;

public void printMessage() {
for(int i = 0; i < 10; i++) {
threadPoolTaskExecutor.execute(new TaskDemo("Hello rayjun " + i));
}
}

public void setThreadPoolTaskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
this.threadPoolTaskExecutor = threadPoolTaskExecutor;
}
}

然后在容器中注入这两个类:

1
2
3
4
5
6
7
8
9
<bean id="taskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="springTaskDemo" class="cn.rayjun.spring.SpringTaskDemo">
<property name="threadPoolTaskExecutor" ref="taskExecutor"/>
</bean>

再通过单元测试来执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:applicationContext.xml")
class SpringTaskDemoTest {

@Autowired
private ApplicationContext context;

@Test
public void test1() {
SpringTaskDemo springTaskDemo = (SpringTaskDemo) context.getBean("springTaskDemo");
springTaskDemo.printMessage();
}
}

控制台中会输出10条消息。

3. TaskScheduler

TaskScheduler 用来执行定时任务,与 TaskExecutor 接口只提供了一个方法不同,TaskScheduler 接口提供了很多方法。

这些方法都接收一个 Runnable 实例,以及表示时间或者频率的参数。定时任务可以配置为执行一次,也可以配置为重复执行。

TaskSchduler 提供的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration
period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration
delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

TaskScheduler 实现

TaskScheduler 有三个实现:

  • ThreadPoolTaskScheduler:使用的比较多,是对 JDK中的 ScheduledThreadPoolExecutor 进行包装
  • ConcurrentTaskScheduler:同样也是对 ScheduledThreadPoolExecutor 进行包装,但是同时也继承了 ConcurrentTaskExecutor 来提供更好的并发度
  • DefaultManagedTaskScheduler:基于 JDNI 规范的实现,功能上与 ConcurrentTaskScheduler 相同

TaskScheduler 的使用

TaskScheduler 的使用和 TaskScheduler 类似。

1
2
3
4
5
6
7
<bean id="taskSchedulerExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="1"/>
</bean>
<bean id="springSchedulerTaskDemo" class="cn.rayjun.spring.SpringSchedulerTaskDemo">
<property name="threadPoolTaskScheduler" ref="taskSchedulerExecutor"/>
</bean>

1
2
3
4
5
6
7
8
9
10
11
public class SpringSchedulerTaskDemo {
private ThreadPoolTaskScheduler threadPoolTaskScheduler;

public void printMessage() {
threadPoolTaskScheduler.schedule(new TaskDemo("Ray"), new CronTrigger("0/5 * * * * ?"));
}

public void setThreadPoolTaskScheduler(ThreadPoolTaskScheduler threadPoolTaskScheduler) {
this.threadPoolTaskScheduler = threadPoolTaskScheduler;
}
}

1
2
SpringSchedulerTaskDemo springTaskDemo = (SpringSchedulerTaskDemo) context.getBean("springSchedulerTaskDemo");
springTaskDemo.printMessage();

执行上面的代码之后,每隔5 秒钟就会打印一次消息。

4. task namespace

在 Spring 中,提供了 task 的 namespace,这样就可以少写很多代码。

在 xml 中假如如下 namespace:

1
2
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"

然后上面创建 TaskExecutor 如下:

1
<task:executor id="taskExecutor2" pool-size="5-10" queue-capacity="25" />

创建 TaskScheduler 如下:

1
<task:scheduler id="threadPoolTaskScheduler" pool-size="1"/>

文 / Rayjun

© 2020 Rayjun    PowerBy Hexo    京ICP备16051220号-1