一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情。
前言
通常我们使用spring boot进行开发web应用的时候,接受一个http请求,然后服务器会分配一个线程来处理这个业务。有的时候我们一个请求处理的业务会有一些耗时的处理。那么如果只有一个线程在同步执行的话,最后响应给客户端的时间就会非常长。这个时候我们需要使用多个线程来帮助我们减少程序的耗时。
使用异步方法
通常如果我们要使用多线程来达到并行执行代码的要求的话,原理上是创建一个线程然后让这个线程执行我们业务中不重要的或者比较耗时的任务。但是如果我们创建一个新的线程,会造成我们的代码和创建一个新线程的代码相互耦合。spring 中给我们提供了一种注解的方式,能巧妙地避开业务代码耦合的问题。
这个注解就是@Async注解。将这个注解标记在一个方法上,那么被标记的方法调用方在调用之后立即返回,而方法实际的执行已经提交给了spring TaskExecutor任务中。通常这些方法的返回都是void。
下面就来看下这个方法注解的使用,我们只需要在方法上加上这个注解即可。
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
@Async
@Override
public void task() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("exec task");
}
}
@Slf4j
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private AsyncService asyncService;
@Override
public void execute() {
asyncService.task();
log.info("execute end");
}
}
我们可以启动一个单元测试来调用taskService的execute方法。
2022-04-24 22:44:51.088 INFO 6064 --- [ main] c.z.s.service.impl.TaskServiceImpl : execute end
2022-04-24 22:44:51.590 INFO 6064 --- [ task-1] c.z.s.service.impl.AsyncServiceImpl : exec task
可以看的首先打印的事execute方法然后再是task。因为加上注解之后,task方法会新启动一个线程,然后当前方法会先立即返回给上层的调用者。然后自己在另一个线程中执行方法。可以看到在日志中两个线程的名字是不同的。
规范的来说我们应该使用线程池来管理线程也就是这样创建一个线程池。
@Bean("selfExecutor")
public Executor selfExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(1);
threadPoolTaskExecutor.setMaxPoolSize(1);
threadPoolTaskExecutor.setQueueCapacity(1);
threadPoolTaskExecutor.setThreadNamePrefix("self-");
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
return threadPoolTaskExecutor;
}
这里主要声明了一个线程池,核心配置的线程大小,最大的线程数大小,当超过最大线程大小后,允许入队队列的大小,最后一个是线程池当这个队列都不能满足所需线程数时,定义一个拒绝策略。 拒绝策略总共有四种。
- DiscardPolicy 对拒绝任务直接无声抛弃,没有异常信息
- DiscardOldestPolicy 抛弃时间最久的未被调用的线程,然后会不断重试,直到被关闭,然后丢弃该线程
- AbortPolicy 对拒绝任务抛弃处理,并且抛出异常
- CallerRunsPolicy 重试当前的任务,直到被关闭,然后丢弃该线程。