工作中进程涉及异步任务,通常是使用多线程技术,比如线程池ThreadPoolExecutor,但使用Executor容易产生OOM,需要手动使用ThreadPoolExecutor创建线程池;在SpringBoot中使用@Async可以实现异步调用,配置线程池参数,可以简单的实现多线程的线程池效果,从而简化开发,避免OOM。
OOM:全称"Out Of Memory",意思就是"内存用完了"。当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可以回收时,就会抛出这个问题。之所以会没有内存,无外乎分配的少,或者就是应用用的太多,并且用完没有释放,浪费了,此时就会造成内存泄漏或者内存溢出。
第一种:无返回异步
1、在启动类上使用@EnableAsync注解
同步执行就是按照代码的顺序执行,而异步执行则是无序,在SpringBoot中使用实现异步调用函数非常简单,首先在启动类上加上@EnableAsync注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableAsync
public class ProjectApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectApplication.class, args);
System.out.println("项目启动成功!");
}
}
2、在方法上标上@sync注解,表示异步调用
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncTest {
@Async
public void taskOne(){
System.out.println("任务一");
System.out.println("================");
}
@Async
public void taskTwo(){
System.out.println("任务二");
System.out.println("++++++++++++++++++");
}
}
3、controller层调用
import com.smile.project.async.utils.AsyncTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AsyncTestController {
@Autowired
AsyncTest asyncTest;
@GetMapping("/testAsync")
public void testAsync(){
asyncTest.taskOne();
asyncTest.taskTwo();
}
}
如果按照同步执行逻辑或先执行任务一,然后再执行任务二,如果是异步执行,则无序,任务一和任务二都有可能先执行
第二种:有返回值调用
有时候要知道任务是否执行完成,根据任务是否执行完成再做其它的业务逻辑,就需要使用到Future接口,其含义是再执行异步任务后会给一个回调函数,我么只要设置回调信息,就可以知道任务是否正确执行完成;
1、我们对异步函数,添加Future返回值类型,使用new AsyncResult<>()设置回调信息
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Component
public class Task {
@Async
public Future<String> taskOne(){
System.out.println("任务一");
return new AsyncResult<>("任务一执行完成");
}
@Async
public Future<String> taskTwo(){
System.out.println("任务二");
return new AsyncResult<>("任务二执行完成");
}
}
2、controller接口调用方法,测试代码
import com.smile.project.async.utils.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@RestController
public class TaskController {
@Autowired
Task task;
@GetMapping("/testTask")
public void testTask() throws Exception {
Future<String> str1 = task.taskOne();
Future<String> str2 = task.taskTwo();
while (true){
//如果任务都做完就执行如下逻辑
if (str1.isDone() && str2.isDone()){
System.out.println(str1.get()+":"+str2.get());
break;
}
}
}
}
执行后输出
线程池
在异步调用中使用的@Async注解,默认的线程池大小如下
#核心线程数
spring.task.execution.pool.core-size=8
#最大线程数
spring.task.execution.pool.max-size=16
#空闲线程存活时间
spring.task.execution.pool.keep-alive=60s
#是否允许核心线程超时
spring.task.execution.pool.allow-core-thread-timeout=true
#线程队列数量
spring.task.execution.pool.queue-capacity-100
#线程关闭等待
spring.task.execution.shutdown.await-termination=false
spring.task.execution.thread-name-prefix=task-
一般情况下,我们都需要手动创建线程池,使用ThreadPoolTaskExecutor类进行配置,设置如下
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 手动创建线程池,
* 使用ThreadPoolTaskExecutor类进行配置
*/
@Configuration
public class PoolConfig {
@Bean
public TaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//设置核心线程数
executor.setCorePoolSize(10);
//设置最大线程数
executor.setMaxPoolSize(15);
//设置队列容量
executor.setQueueCapacity(20);
//设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
//设置默认线程名称
executor.setThreadNamePrefix("smile-");
//设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
在task类上加上新的一个方法如下:
@Async
public void sayHello(String name){
LoggerFactory.getLogger(Task.class).info(name + ":Hello World");
}
执行结果如下,日志打印除线程池名称为smile-3
有时候,一个项目中如果配置了多个线程池,如下所示
在使用@Async注解时就需要指明具体使用的线程池,如下格式