文章内容已经收录在《高级技术专家成长笔记》,欢迎订阅专栏!
从原理、系统设计出发,直击面试难点,实现更高维度的降维打击!
多线程案例落地 - 库存扣减请求合并、库存一致性保证
海哥也给出了合并库存扣减请求的 Demo,是一个多线程的 Demo,对提升并发掌握很有帮助,这里也整理一篇文章进行分析!
参考 B 站 up 主《极海Channel》,视频链接:https://www.bilibili.com/video/BV1Hv4y1P7ta
背景
对于库存扣减流程中的优化,最主要的优化就是库存扣减请求的合并,海哥也给出了 Demo,这里将整体设计、演进思路整理出来
库存扣减请求整体合并思路:针对热门商品进行合并,因此需要判断商品并发数,如果超过阈值,则判定为比较热门的商品,进行库存扣减请求的合并,此时会将库存扣减请求提交到队列,并且将用户线程阻塞,等待异步线程处理,处理完成之后,通知用户线程响应结果即可
这里会对海哥给出的代码进行分析,并且整理评论区中给出的问题
库存扣减请求的合并,主要流程为:
- 用户提交库存扣减请求
- 将用户请求的库存扣减请求入队,用户线程阻塞等待 200ms,等待异步线程处理请求
- 异步线程从队列中取出用户扣减请求,进行合并,再执行库存的扣减
- 异步线程处理完之后,唤醒用户线程,并响应结果
接下来将各个步骤拆解开,来分析库存扣减合并的 Demo
1、库存扣减操作
先看库存扣减操作的实现,主要是用户请求进来之后,需要将用户请求加入到队列中,等待异步线程处理,此时用户线程阻塞等待处理,阻塞时间为 200ms
这里就涉及到了阻塞,采用了 wait()
来实现阻塞
对应代码如下,对于阈值判断、队列的创建,不是核心逻辑,先略过,这里主要关注用户请求的入队操作,以及阻塞等待操作如何实现:
// 定义阻塞队列,存放用户请求
private BlockingQueue<RequestPromise> queue = new LinkedBlockingQueue<>(10);
public Result operate(UserRequest userRequest) throws InterruptedException {
// TODO 阈值判断
// TODO 队列的创建
// 1、创建 RequestPromise 封装用户请求,通过该对象,实现用户线程的阻塞和唤醒
RequestPromise requestPromise = new RequestPromise(userRequest);
synchronized (requestPromise) {
// 2、用户请求入队
boolean enqueueSuccess = queue.offer(requestPromise, 100, TimeUnit.MILLISECONDS);
if (! enqueueSuccess) {
return new Result(false, "系统繁忙");
}
try {
// 3、用户请求阻塞 200ms,等待请求被处理
requestPromise.wait(200);
// 4、根据 Result 判断是否为用户等待 200ms 超时
if (requestPromise.getResult() == null) {
return new Result(false, "等待超时");
}
} catch (InterruptedException e) {
return new Result(false, "被中断");
}
}
return requestPromise.getResult();
}
核心有两个操作:
queue.offer()
:将用户请求入队,之后异步线程会从队列取出请求,进行 合并处理requestPromise.wait(200)
:将用户线程阻塞 200ms,等待处理,如果 200ms 之内,异步线程仍然没有处理完毕,会判断requestPromise.getResult() == null
,就算 等待超时 ,此时,直接返回结果为扣减失败即可
这里需要将入队操作放在 synchronized 同步代码块内(后边会详细解释):
这里假如将 queue.offer()
放在 synchronized 外部,那么假如用户请求刚加入队列之后,该请求就立即被异步线程处理,异步线程处理之后会通过 notify()
去唤醒这个用户线程,此时用户线程还没有执行 wait()
操作,也就是 no