大家都知道从JDK5开始引入JUC工具包(java.util .concurrent),JUC中包含了atomic原子类、 Lock锁、Executor框架、Callable与Future、ConcurrentMap等支持多线程高并发的Java类,程序员可以调用此包进行高效的多线程编程。Java线程池的功能就是通过JUC包中Executor框架来实现的,在Executor中为我们提供了五种线程池,比如固定大小线程池、单线程池、可以固定周期频率的线程池等。Spring中也有线程池的功能,它是通过继承java的Executor接口来实现的,Spring也提供了在不同功能场景的线程池,大概有7种,方便我们的使用。下面我们先通过一幅UML类图,了解一下Spring中各种线程池的继承关系:
接下来我们了解一下Spring中各种线程池的功能:
线程池名称 | 作用及特点 |
---|---|
SimpleAsyncTaskExecutor | 每次调用创建一个新的线程,线程不会重用;支持设置设置最大的线程数量,超过最大线程数,堵塞调用。 |
SyncTaskExecutor | 同步线程,每次调用都在同一个线程中执行。 |
ConcurrentTaskExecutor | Executor的包装类,不推荐使用。 |
SimpleThreadPoolTaskExecutor | 它是Quartz中SimpleThreadPool类的子类,它会监听Spring的生命周期回调。使用场景:需要在Quartz和非Quartz组件中共用时使用。 |
ThreadPoolTaskExecutor | ThreadPoolExecutor的包装类,最常用Spring线程池。 |
TimerTaskExecutor | 它使用TimerTask类作为实现。 |
WorkManagerTaskExecutor | 它现了WorkManager接口,实现使用了CommonJ WorkManager作为其底层实现。其中,CommonJ 是BEA和IBM联合开发的一套规范。这些规范并非Java EE的标准,但它是BEA和IBM的应用服务器实现的共同标准。 |
ThreadPoolTaskExecutor的注解配置方式举例:
package com.hanxiaozhang.taskexecutor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 〈一句话功能简述〉
* 〈线程池配置〉
*
* @author hanxinghua
* @create 2020/2/22
* @since 1.0.0
*/
@Configuration
public class ThreadPoolTaskExecutorConfig implements AsyncConfigurer {
/**
* 冒号后为默认值
*/
@Value("${async.executor.core-pool-size:10}")
private int corePoolSize;
/**
* 默认值也可以直接赋值给属性
*/
@Value("${async.executor.max-pool-size}")
private int maxPoolSize=100;
@Value("${async.executor.queue-capacity:10}")
private int queueCapacity;
@Value("${async.executor.await-termination-seconds:60}")
private int awaitTerminationSeconds;
@Value("${async.executor.wait-for-tasks-to-complete-on-shutdown:true}")
private boolean waitForTasksToCompleteOnShutdown;
@Value("${async.executor.thread-name-prefix:AsyncExecutorThread-}")
private String threadNamePrefix;
@Bean(name = "asyncExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
//设置核心线程数
threadPool.setCorePoolSize(corePoolSize);
//设置最大线程数
threadPool.setMaxPoolSize(maxPoolSize);
//线程池所使用的缓冲队列
threadPool.setQueueCapacity(queueCapacity);
//等待任务在关机时完成--表明等待所有线程执行完
threadPool.setWaitForTasksToCompleteOnShutdown(waitForTasksToCompleteOnShutdown);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
threadPool.setAwaitTerminationSeconds(awaitTerminationSeconds);
// 线程名称前缀
threadPool.setThreadNamePrefix(threadNamePrefix);
// 初始化线程
threadPool.initialize();
return threadPool;
}
/**
* 多线程异常处理
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
@Async的作用是定义异步任务,它一共有三种用法:1. 最简单的无参数无返回值异步调用; 2. 带参数无返回值异步调用 3. 返回Future异步调用,下面三种用法简单Demo:
/**
* 1.最简单的无参数无返回值异步调用
*/
@Async
public void asyncSimple() {
log.info("asyncSimple");
}
/**
* 2.带参数无返回值异步调用
*
* @param s
*/
@Async
public void asyncParameter(String s) {
log.info("asyncParameter, parameter={}", s);
}
/**
* 3.返回Future异步调用
*
* @param s
* @return
*/
@Async
public Future asyncReturnFuture(String s) {
log.info("asyncReturnFuture, parameter={}", s);
Future future=new AsyncResult("futureString: " + s);
return future;
}
此外, @Async必须与@EnableAsync一起使用才有效果,如果没有在该类上或Springboot启动类上打@EnableAsync,异步是不生效的。大家可以通过以下例子进验证,我们模拟一个流程A,流程A一共有step1、step2和step3三个步骤,执行完step1后,step2就可以异步处理。
//---------------模拟流程A------------------
@Component
public class AsyncAnnotationCode {
/**
* 模拟步骤1,执行100ms
*
* @throws InterruptedException
*/
public void step1() throws InterruptedException {
System.out.println("step1 start");
Thread.sleep(100);
System.out.println("step1 end");
}
/**
* 模拟步骤2,执行300ms
*
* @throws InterruptedException
*/
public void step2() throws InterruptedException {
System.out.println("step2 start");
Thread.sleep(300);
System.out.println("step2 end");
}
/**
* 模拟步骤3,执行200ms
*
* @throws InterruptedException
*/
public void step3() throws InterruptedException {
System.out.println("step3 start");
Thread.sleep(200);
System.out.println("step3 end");
}
}
//---------------执行流程A------------------
@Test
public void asyncTest() {
try {
long startTime = System.currentTimeMillis();
asyncAnnotationCode.step1();
asyncAnnotationCode.step2();
asyncAnnotationCode.step3();
long endTime = System.currentTimeMillis();
System.out.println("流程耗时:"+(endTime-startTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
通过以上测试,我们可以发现step1-3是顺序执行,耗时在600ms左右。
//---------------模拟流程A------------------
@Component
public class AsyncAnnotationCode {
/**
* 模拟步骤1,执行100ms
*
* @throws InterruptedException
*/
public void step1() throws InterruptedException {
System.out.println("step1 start");
Thread.sleep(100);
System.out.println("step1 end");
}
/**
* 模拟步骤2,执行300ms
*
* @throws InterruptedException
*/
@Async
public void step2() throws InterruptedException {
System.out.println("step2 start");
Thread.sleep(300);
System.out.println("step2 end");
}
/**
* 模拟步骤3,执行200ms
*
* @throws InterruptedException
*/
public void step3() throws InterruptedException {
System.out.println("step3 start");
Thread.sleep(200);
System.out.println("step3 end");
}
}
//---------------执行流程A------------------
@Test
public void asyncTest() {
try {
long startTime = System.currentTimeMillis();
asyncAnnotationCode.step1();
asyncAnnotationCode.step2();
asyncAnnotationCode.step3();
long endTime = System.currentTimeMillis();
System.out.println("流程耗时:"+(endTime-startTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
通过以上测试,只添加@Async,step1-3还是是顺序执行,耗时在600ms,并没有异步执行。
//---------------模拟流程A------------------
@Component
@EnableAsync
public class AsyncAnnotationCode {
/**
* 模拟步骤1,执行100ms
*
* @throws InterruptedException
*/
public void step1() throws InterruptedException {
System.out.println("step1 start");
Thread.sleep(100);
System.out.println("step1 end");
}
/**
* 模拟步骤2,执行300ms
*
* @throws InterruptedException
*/
@Async
public void step2() throws InterruptedException {
System.out.println("step2 start");
Thread.sleep(300);
System.out.println("step2 end");
}
/**
* 模拟步骤3,执行200ms
*
* @throws InterruptedException
*/
public void step3() throws InterruptedException {
System.out.println("step3 start");
Thread.sleep(200);
System.out.println("step3 end");
}
}
//---------------执行流程A------------------
@Test
public void asyncTest() {
try {
long startTime = System.currentTimeMillis();
asyncAnnotationCode.step1();
asyncAnnotationCode.step2();
asyncAnnotationCode.step3();
long endTime = System.currentTimeMillis();
System.out.println("流程耗时:"+(endTime-startTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
通过以上测试,同时@Async和@EnableAsync后,step1-3没有按照是顺序执行,step2 start后,step3 start开始了,耗时344ms。细心的小伙伴可能发现,输出的流程耗时不正确啊,并且在输出step2 end 前就输出了流程耗时。此外,通过对模拟流程分析,能计算异步大概的耗时时间,step1耗时100ms + step2耗时300ms,大概是400ms,也证明耗时间不正确。接下来,咱们分析一下为什么会这样,我们这里要用到Thread.currentThread()方法,该方法的作用是获取当前线程信息。
//---------------模拟流程A------------------
@Component
@EnableAsync
public class AsyncAnnotationCode {
/**
* 模拟步骤1,执行100ms
*
* @throws InterruptedException
*/
public void step1() throws InterruptedException {
System.out.println("step1 start");
System.out.println("step1 当前线程名称:"+Thread.currentThread().getName());
Thread.sleep(100);
System.out.println("step1 end");
}
/**
* 模拟步骤2,执行300ms
*
* @throws InterruptedException
*/
@Async
public void step2() throws InterruptedException {
System.out.println("step2 start");
System.out.println("step2 当前线程名称:"+Thread.currentThread().getName());
Thread.sleep(300);
System.out.println("step2 end");
}
/**
* 模拟步骤3,执行200ms
*
* @throws InterruptedException
*/
public void step3() throws InterruptedException {
System.out.println("step3 start");
System.out.println("step3 当前线程名称:"+Thread.currentThread().getName());
Thread.sleep(200);
System.out.println("step3 end");
}
}
//---------------执行流程A------------------
@Test
public void asyncTest() {
try {
long startTime = System.currentTimeMillis();
System.out.println("执行方法 当前线程名称:"+Thread.currentThread().getName());
asyncAnnotationCode.step1();
asyncAnnotationCode.step2();
asyncAnnotationCode.step3();
long endTime = System.currentTimeMillis();
System.out.println("流程耗时:"+(endTime-startTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
通过以上结果,我们可以发现执行方法、step1、step3是在main线程,而step2是在AsyncExecutorThread-1线程,main线程代码会安装顺序执行,执行方法startTime-> step1()->step3()->endTime->输出耗时时间,所以这里耗时时间是step1+step3的大概时间300ms。细心的小伙伴可能又会发现,step2的线程名称是AsyncExecutorThread-1,看名字应该是使用了Spring线程池,是的,你没有想错,我确实配置了线程池,如果没有配置线程池,运行结果是什么样的呢?
结果情况一:没有异常,但是没有输出step2 end
结果情况二:有异常, 也没有输出step2 end
经过一番思考,我觉得造成没有输出step2 end的原因是,采用单元测试的方式进行测试时,执行测试的代码在main线程输出流程耗时后,就结束了,main线程结束后,单元测试也就跟着结束了,所有没有输出step2 end。应该在执行测试代码中添加一个while(true)循环,让main线程永不停止,即可解决。
//---------------执行流程A------------------
@Test
public void asyncTest() {
try {
long startTime = System.currentTimeMillis();
System.out.println("执行方法 当前线程名称:"+Thread.currentThread().getName());
asyncAnnotationCode.step1();
asyncAnnotationCode.step2();
asyncAnnotationCode.step3();
long endTime = System.currentTimeMillis();
System.out.println("流程耗时:"+(endTime-startTime));
//main线程一直不消亡
while (true){
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}