最近在做项目的并发测试,发现在目前的项目中,小伙伴们在web中直接使用了多线程来处理数据,示例代码如下:
备注:
1、由于是敏感项目,所以不方便直接贴源代码。 2、这里的演示代码仅是模拟目前项目中的使用方法。 3、代码中还有在多线程中执行数据库操作的动作
从项目中这样的用法其实还是可以看到小伙伴比较用心的,主要可以看到有2个优点:
1、因为当前的接口比较耗时,所以这里为了快速的给客户端进行反应,把一些复杂的事情使用异步线程来说。这是一个考虑比较周到的想法。 2、线程这边使用线程池来实现异步线程。
但是从代码的实现也能看到几个比较明确的缺点:
1、这里使用异步线程来操作数据库,如果此时服务被kill掉,那么业务逻辑的完整性就会缺失,整个链路的数据会不完整。(事务不完整) 2、这里使用多线程当出现并发的时候,线程池由主线程进行维护,不能实现正确的关闭。 3、这里的线程池的数量配置没有结合实际情况来进行操作。
所以这里的话我们就来改进下,体现一下在springboot这种web项目中多线程的正确使用方式。这里的话其实我们主要的核心是把线程池放到勾子里面去,然后让线程池在jvm关闭之前,利用勾子先把线程池给关闭了,此时就能合理的保证整个程序的正确性。那么怎么做呢?
1)创建一个线程池的类
首先我们需要创建一个多线程的管理类,里面主要包含的功能有:
1、创建线程池。 2、利用线程池执行任务 3、执行线程池的销毁任务
依据上诉3点需求,完整的线程池管理类的示例代码如下:
package com.spring.threads.utils; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolUtils { /** * 这里的线程数需要按照实际情况进行调整,本测试案例使用的线程池数无法支撑上千的并发 */ private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4096), new ThreadPoolExecutor.CallerRunsPolicy()); /** * execute task in thread pool */ public static void execute(Runnable command) { executor.execute(command); } public static <T> Future<T> shumit(Callable<T> task) { return executor.submit(task); } public static void shutdown() { if (executor != null) { executor.shutdown(); } } }
2)在starter类里面添加勾子,让这个线程池在jvm关闭之前关闭线程池,示例代码如下:
Runtime.getRuntime().addShutdownHook(new Thread(() -> { ThreadPoolUtils.shutdown(); }));
这里我们看到Runtime.getRuntime().addShutdownHook这个方法是不是很熟悉,就是一个勾子,这个具体的方法需要我们自己实现,我们实现的就是关闭线程池。
3)在代码里面使用这个线程池
代码里面需要使用线程池,直接使用管理类提交任务即可,示例代码如下:
ThreadPoolUtils.execute(() -> { try { TimeUnit.SECONDS.sleep(RANDOM.nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这是需要异步线程执行的内容"); });
以上就是正确的在springboot这种web项目中使用多线程的案例。最后附上测试的结果图:
kill的时候经过短暂的停顿后,服务就停掉了。
总结:
1、在springboot中,使用异步多线程需要谨慎,里面千万不要做和业务数据相关的逻辑,不然无法保证事务的完整性,也就无法保证业务数据的完整性了。 2、在sporingboot中,使用异步多线程一般我们常用的主要是:打印日志,计数等可以容忍数据缺失的场景。 3、切记关闭多线程.
最后按照惯例,附上本案例的源码,登录后即可下载。
还没有评论,来说两句吧...