ThreadPoolExecutor线程池、Queue缓冲队列实现高并发模拟下单业务
主要是在项目中(中小型项目) 有支付下单业务(只是办理下单,没有涉及到商品库存),目前用户量还没有上来,没有出现问题,但是想到如果用户量变大,下单并发量变大,可能会出现一系列的问题,趁着空闲时间,做了这个demo测试相关问题。
可能遇到的问题如下:
1.订单重复
2.高并发下,性能变慢
解决方式:ThreadPoolExecutor线程池 + Queue队列
开发工具:IDEA 15
1.首先是springBoot的项目框架如下:
2.业务测试流程涉及的类,如下:
BusinessThread 类:
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 40 41 | package com.springboot.demo.Threads; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** * 业务线程类 */ @Component @Scope ( "prototype" ) //spring 多例 public class BusinessThread implements Runnable{ private String acceptStr; public BusinessThread(String acceptStr) { this .acceptStr = acceptStr; } public String getAcceptStr() { return acceptStr; } public void setAcceptStr(String acceptStr) { this .acceptStr = acceptStr; } @Override public void run() { //业务操作 System.out.println( "多线程已经处理订单插入系统,订单号:" +acceptStr); //线程阻塞 /*try { Thread.sleep(1000); System.out.println("多线程已经处理订单插入系统,订单号:"+acceptStr); } catch (InterruptedException e) { e.printStackTrace(); }*/ } } |
TestThreadPoolManager 类:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | package com.springboot.demo.Threads; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.stereotype.Component; import java.util.Map; import java.util.Queue; import java.util.concurrent.*; /** * 线程池+阻塞队列类 */ @Component public class TestThreadPoolManager implements BeanFactoryAware { //用于从IOC里取对象 private BeanFactory factory; //如果实现Runnable的类是通过spring的application.xml文件进行注入,可通过 factory.getBean()获取,这里只是提一下 // 线程池维护线程的最少数量 private final static int CORE_POOL_SIZE = 2 ; // 线程池维护线程的最大数量 private final static int MAX_POOL_SIZE = 10 ; // 线程池维护线程所允许的空闲时间 private final static int KEEP_ALIVE_TIME = 0 ; // 线程池所使用的缓冲队列大小 private final static int WORK_QUEUE_SIZE = 50 ; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { factory = beanFactory; } /** * 用于储存在队列中的订单,防止重复提交,在真实场景中,可用redis代替 验证重复 */ Map<String, Object> cacheMap = new ConcurrentHashMap<>(); /** * 订单的缓冲队列,当线程池满了,则将订单存入到此缓冲队列 */ Queue<Object> msgQueue = new LinkedBlockingQueue<Object>(); /** * 当线程池的容量满了,执行下面代码,将订单存入到缓冲队列 */ final RejectedExecutionHandler handler = new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { //订单加入到缓冲队列 msgQueue.offer(((BusinessThread) r).getAcceptStr()); System.out.println( "系统任务太忙了,把此订单交给(调度线程池)逐一处理,订单号:" + ((BusinessThread) r).getAcceptStr()); } }; /**创建线程池*/ final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue(WORK_QUEUE_SIZE), this .handler); /**将任务加入订单线程池*/ public void addOrders(String orderId){ System.out.println( "此订单准备添加到线程池,订单号:" + orderId); //验证当前进入的订单是否已经存在 if (cacheMap.get(orderId) == null ) { cacheMap.put(orderId, new Object()); BusinessThread businessThread = new BusinessThread(orderId); threadPool.execute(businessThread); } } /** * 线程池的定时任务----> 称为(调度线程池)。此线程池支持 定时以及周期性执行任务的需求。 */ final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 5 ); /** * 检查(调度线程池),每秒执行一次,查看订单的缓冲队列是否有 订单记录,则重新加入到线程池 */ final ScheduledFuture scheduledFuture = scheduler.scheduleAtFixedRate( new Runnable() { @Override public void run() { //判断缓冲队列是否存在记录 if (!msgQueue.isEmpty()){ //当线程池的队列容量少于WORK_QUEUE_SIZE,则开始把缓冲队列的订单 加入到 线程池 if (threadPool.getQueue().size() < WORK_QUEUE_SIZE) { String orderId = (String) msgQueue.poll(); BusinessThread businessThread = new BusinessThread(orderId); threadPool.execute(businessThread); System.out.println( "(调度线程池)缓冲队列出现订单业务,重新添加到线程池,订单号:" +orderId); } } } }, 0 , 1 , TimeUnit.SECONDS); /**获取消息缓冲队列*/ public Queue<Object> getMsgQueue() { return msgQueue; } /**终止订单线程池+调度线程池*/ public void shutdown() { //true表示如果定时任务在执行,立即中止,false则等待任务结束后再停止 System.out.println( "终止订单线程池+调度线程池:" +scheduledFuture.cancel( false )); scheduler.shutdown(); threadPool.shutdown(); } } |
TestController 类:
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 40 41 42 43 44 45 46 47 48 49 50 | package com.springboot.demo; import com.springboot.demo.Threads.TestThreadPoolManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.Queue; import java.util.UUID; /** * 测试类 */ @RestController public class TestController { @Autowired TestThreadPoolManager testThreadPoolManager; /** * 测试模拟下单请求 入口 * @param id * @return */ @GetMapping ( "/start/{id}" ) public String start( @PathVariable Long id) { //模拟的随机数 String orderNo = System.currentTimeMillis() + UUID.randomUUID().toString(); testThreadPoolManager.addOrders(orderNo); return "Test ThreadPoolExecutor start" ; } /** * 停止服务 * @param id * @return */ @GetMapping ( "/end/{id}" ) public String end( @PathVariable Long id) { testThreadPoolManager.shutdown(); Queue q = testThreadPoolManager.getMsgQueue(); System.out.println( "关闭了线程服务,还有未处理的信息条数:" + q.size()); return "Test ThreadPoolExecutor start" ; } } |
3.使用postman或者JMeter等工具压力测试(自行百度~)
4.打印的日志说明,开始的订单直接执行插入到系统,当线程池的容量已经满了,则使用RejectedExecutionHandler方法把后面的订单添加到 Queue缓冲队列,使用ScheduledFuture方法定时(我这里是每秒一次)检查Queue队列,重新把队列里面的订单添加到线程池,执行后面的插入任务。部分日志如下:
到此,就演示完毕了,朋友们可以自行测试下~
分类:
线程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具