Spring中的线程池与任务调度
线程池已经成为 Java 开发中必不可少的一个组件了,在使用 Spring 时,不需要自己重头去使用线程池。
Spring 已经提供了非常完备的封装,可以直接使用 Spring 提供的接口。
本文基于 Spring5.3 和 OpenJDK11
1. Spring 中的任务
Spring 中对与任务的执行提供了两种抽象, TaskExecutor
和 TaskScheduler
,分别表示执行异步任务和定时任务。
Executor
在 JDK 中是线程池的名称。一个 executor 用来表示执行任务的线程池,其中最少会有一个线程,每个线程都可以用来执行同步或者异步任务。
Scheduler
表示的是定时任务,定时任务的触发,支持 JDK 中的 Timer
和 Quartz 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 的使用。
创建一个待执行的任务:
public class TaskDemo implements Runnable{
private String message;
public TaskDemo(String message) {
this.message = message;
}
@Override
public void run() {
System.out.println(message);
}
}
再创建一个执行任务的执行器:
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;
}
}
然后在容器中注入这两个类:
<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>
再通过单元测试来执行代码:
@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 提供的方法如下:
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 类似。
<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>
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;
}
}
SpringSchedulerTaskDemo springTaskDemo = (SpringSchedulerTaskDemo) context.getBean("springSchedulerTaskDemo");
springTaskDemo.printMessage();
执行上面的代码之后,每隔5 秒钟就会打印一次消息。
4. task namespace
在 Spring 中,提供了 task 的 namespace,这样就可以少写很多代码。
在 xml 中假如如下 namespace:
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 如下:
<task:executor id="taskExecutor2" pool-size="5-10" queue-capacity="25" />
创建 TaskScheduler 如下:
<task:scheduler id="threadPoolTaskScheduler" pool-size="1"/>
文 / Rayjun