日常开发中,我们偶尔会遇到在业务层中需要同时修改多张表的数据并需要有序的执行,如果用往常的同步的方式,也就是单线程的方式来执行的话,可能会出现执行超时等异常造成请求结果失败,及时成功,前端也需要等待较长时间来获取响应结果,这样不但造成了用户体验差,而且会经常出现请求执行失败的问题,在这里我们一般会采用3种方式来处理。
在采用三种方式之前,我们所有来观察一下使用同步的方式实现的结果:
1.我们定义一个AsyncController,如下所示:
@Api(value = "异步测试",tags = "异步测试") @RestController public class AsyncController { @Autowired private AsyncService asyncService; /** * 使用传统方式测试 */ @GetMapping("/test") @ApiOperation(value = "测试异步处理") public void test() { System.out.println("获取主线程名称:" + Thread.currentThread().getName()); asyncService.execute(); System.out.println("执行成功,返回结果"); } }
2. 我们定义AsyncService,如下所示:
@Service public class AsyncService { public void execute() { // 这里执行实际的业务逻辑,在这里我们就是用一个简单的遍历来模拟 Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).forEach(t -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("获取number为:" + t); }); } }
执行请求http://localhost:8889/test,结果显示如下:
获取主线程名称:http-nio-8889-exec-4 获取number为:1 获取number为:2 获取number为:3 获取number为:4 获取number为:5 获取number为:6 获取number为:7 获取number为:8 获取number为:9 获取number为:10 执行成功,返回结果
接下来我们采用我们所说的3种方式来实现:
1.使用线程池的方式来实现
修改AsyncController中的test()方法,service层不变,如下所示:
@Api(value = "异步测试",tags = "异步测试") @RestController public class AsyncController { @Autowired private AsyncService asyncService; /** * 使用传统方式测试 */ @GetMapping("/test") @ApiOperation(value = "测试异步处理") public void test() { System.out.println("获取主线程名称:" + Thread.currentThread().getName()); /** * 定义一个线程池 * 核心线程数(corePoolSize):1 * 最大线程数(maximumPoolSize): 1 * 保持连接时间(keepAliveTime):50000 * 时间单位 (TimeUnit):TimeUnit.MILLISECONDS(毫秒) * 阻塞队列 new LinkedBlockingQueue<Runnable>() */ ThreadPoolExecutor executor = new ThreadPoolExecutor(1,5,50000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // 执行业务逻辑方法serviceTest() executor.execute(new Runnable() { @Override public void run() { asyncService.execute(); } }); System.out.println("执行成功,返回结果"); } }
执行请求http://localhost:8889/test,结果显示如下:
获取主线程名称:http-nio-8889-exec-1 执行成功,返回结果 获取number为:1 获取number为:2 获取number为:3 获取number为:4 获取number为:5 获取number为:6 获取number为:7 获取number为:8 获取number为:9 获取number为:10
我们发现在主线程中使用线程池中的线程来实现,程序的执行结果表明,主线程直接将结果进行了返回,然后才是线程池在执行业务逻辑,减少了请求响应时长。
2. 使用注解@EnableAsync和@Async来实现
第一种方式虽然实现了我们想要的结果,但是,我们发现如果我们在多个请求中都需要这种异步请求,每次都要写这么冗余的线程池配置,所以spring为了提升开发人员的开发效率,使用@EnableAsync来开启异步的支持,使用@Async来对某个方法进行异步执行。AsyncController如下所示:
@Api(value = "异步测试",tags = "异步测试") @RestController public class AsyncController { @Autowired private AsyncService asyncService; @GetMapping("/test") @ApiOperation(value = "测试异步处理") public void test() { System.out.println("获取主线程名称:" + Thread.currentThread().getName()); asyncService.execute(); System.out.println("执行成功,返回结果"); } }
AsyncService如下所示:
@Service @EnableAsync public class AsyncService { /** * 采用异步执行 */ @Async public void execute() { // 这里执行实际的业务逻辑,在这里我们就是用一个简单的遍历来模拟 Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).forEach(t -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("获取number为:" + t); }); } }
执行请求http://localhost:8889/test,结果显示如下:
获取主线程名称:http-nio-8889-exec-1 执行成功,返回结果 获取number为:1 获取number为:2 获取number为:3 获取number为:4 获取number为:5 获取number为:6 获取number为:7 获取number为:8 获取number为:9 获取number为:10
结果与使用线程池的结果一致,简化了我们编写代码的逻辑,@EnableAsync和@Async注解帮我们实现了避免创建线程池的繁琐,提高了我们的开发效率,@EnableAsync也可以写在启动类上。
3. 使用消息队列(mq)来实现
当我们涉及的请求在业务逻辑中一次性操作很多很多的数据,例如:一个请求执行业务操作后,将操作日志插入到数据库中,我们可以使用@Async来实现,但是这样增加了业务和非业务关系的冗余性,同时如何并发量很大,我们使用@Async处理,一旦服务器宕机,尚未处理的任务会尽数丢失,为了避免这种情况,我们会采用mq来实现,将业务逻辑和非业务逻辑进行隔离执行,互不影响,非业务逻辑不会影响到执行业务逻辑的结果和主机性能,对于mq的处理,我会单独写一篇文章来详细介绍。
文章地址:xxxxx