SpringBoot——异步(二十六)

前言

在SpringBoot中使用异步调用是很简单的,只需要使用@Async注解即可实现方法的异步调用。

简单异步

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.qn;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;

import java.util.Arrays;

/**
* @ServletComponentScan 扫描Servlet, Filter, Listener 添加到容器
*/
@MapperScan("com.qn.mapper") //扫描的mapper
@SpringBootApplication
@ServletComponentScan
@EnableAsync
public class SpringbootDemoApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}

@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
// 开始检查spring boot 提供的 beans
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}
}

Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qn.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
@Async
public void asyncEvent() throws InterruptedException {
//休眠1s
Thread.sleep(1000);
//log.info("异步方法输出:{}!", System.currentTimeMillis());
}

public void syncEvent() throws InterruptedException {
Thread.sleep(1000);
//log.info("同步方法输出:{}!", System.currentTimeMillis());
}
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.qn.service;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* jdbc测试
*/
@SpringBootTest
@RunWith(SpringRunner.class)
class AccountServiceTest {

@Autowired
AccountService accountService;

@Test
void getAll() {
accountService.getAll().forEach(account -> System.out.println(account.toString()));
}
}

注意

在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。

调用的异步方法,不能为同一个类的方法,简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。

异步线程池

自定义线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.qn.config.AsnycConfig;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@SpringBootConfiguration
public class TaskExecutorConfig {
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(20);
taskExecutor.setMaxPoolSize(200);
taskExecutor.setQueueCapacity(25);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("oKong-");
// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
taskExecutor.setAwaitTerminationSeconds(60);
taskExecutor.initialize();
return taskExecutor;
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.qn.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;

@Service
public class AsyncTaskService {

@Async("asyncPoolTaskExecutor")
public Future<String> asyncEvent() throws InterruptedException {
//休眠1s
Thread.sleep(1000);
System.out.println("异步方法内部线程名称:{}!" + Thread.currentThread().getName());
return new AsyncResult<>("异步方法返回值");
}

public void syncEvent() throws InterruptedException {
Thread.sleep(1000);
//log.info("同步方法输出:{}!", System.currentTimeMillis());
}
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.qn.service;

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.Future;

@SpringBootTest
@RunWith(SpringRunner.class)
class AsyncTaskServiceTest {
private AsyncTaskService asyncTaskService;

public String doAsync() throws InterruptedException {
long start = System.currentTimeMillis();
System.out.println("方法执行开始:{}" + start);
//调用同步方法
asyncTaskService.syncEvent();

long syncTime = System.currentTimeMillis();
System.out.println("同步方法用时:{}" + (syncTime - start));
//调用异步方法
Future<String> doFutrue = asyncTaskService.asyncEvent();
while(true) {
//判断异步任务是否完成
if(doFutrue.isDone()) {
break;
}
Thread.sleep(100);
}
long asyncTime = System.currentTimeMillis();
System.out.println("异步方法用时:{}" + (asyncTime - syncTime));
System.out.println("方法执行完成:{}!" + asyncTime);
return "async!!!";
}

}

注意

对于一些需要异步回调的函数,不能无期限的等待下去,所以一般上需要设置超时时间,超时后可将线程释放,而不至于一直堵塞而占用资源。

配置future超时:

1
2
3
/get方法会一直堵塞,直到等待执行完成才返回
//get(long timeout, TimeUnit unit) 在设置时间类未返回结果,会直接排除异常TimeoutException,messages为null
String result = doFutrue.get(60, TimeUnit.SECONDS);//60s
文章目录
  1. 1. 前言
  2. 2. 简单异步
    1. 2.1. 启动类
    2. 2.2. Service
  3. 3. Test
    1. 3.1. 注意
  4. 4. 异步线程池
    1. 4.1. 自定义线程池
    2. 4.2. service
    3. 4.3. Test
  5. 5. 注意
|