Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

30 | 生产者消费者模式:电商库存设计优化

刘超 2019-07-30
你好,我是刘超。
生产者消费者模式,在之前的一些案例中,我们是有使用过的,相信你有一定的了解。这个模式是一个十分经典的多线程并发协作模式,生产者与消费者是通过一个中间容器来解决强耦合关系,并以此来实现不同的生产与消费速度,从而达到缓冲的效果。
使用生产者消费者模式,可以提高系统的性能和吞吐量,今天我们就来看看该模式的几种实现方式,还有其在电商库存中的应用。

Object 的 wait/notify/notifyAll 实现生产者消费者

第 16 讲中,我就曾介绍过使用 Object 的 wait/notify/notifyAll 实现生产者消费者模式,这种方式是基于 Object 的 wait/notify/notifyAll 与对象监视器(Monitor)实现线程间的等待和通知。
还有,在第 12 讲中我也详细讲解过 Monitor 的工作原理,借此我们可以得知,这种方式实现的生产者消费者模式是基于内核来实现的,有可能会导致大量的上下文切换,所以性能并不是最理想的。

Lock 中 Condition 的 await/signal/signalAll 实现生产者消费者

相对 Object 类提供的 wait/notify/notifyAll 方法实现的生产者消费者模式,我更推荐使用 java.util.concurrent 包提供的 Lock && Condition 实现的生产者消费者模式。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(27)

  • 杨俊
    我的理解是库存放缓存,用户提交订单在缓存扣减库存,用户端能够快速返回显示订单提交成功并支付,然后只有支付成功之后才会利用队列实际的扣减数据库库存是吗?要是不支付会在缓存补回库存吧,应该会限时支付吧

    作者回复: 对的

    2019-07-30
    5
  • QQ怪
    在网关层中把请求放入到mq中,后端服务从消费队列中消费消息并处理;或者用有固定容量的消费队列的令牌桶,令牌发生器预估预计的处理能力,匀速生产放入令牌队列中,满了直接丢弃,网关收到请求之后消费一个令牌,获得令牌的请求才能进行后端秒杀请求,反之直接返回秒杀失败

    作者回复: 大家都一致想到了限流,限流是非常必要的,无论我们的程序优化的如何,还是有上限的,限流则是一种兜底策略。

    除了这个,我们还可以使用协程来优化线程由于阻塞等待带来的上下文切换性能问题,可以回顾第19讲,我们也用协程实现过生产者消费者模式。

    2019-07-30
    4
    2
  • 撒旦的堕落
    网关与服务之间增加令牌桶 或者mq 以保护秒杀服务不会被大的流量压垮 可以么

    作者回复: 可以的,很通用的一种解决方案

    2019-07-30
    1
  • Geek_f6fba7
    老师,您那个第二个生产者消费者模式实现我运行了会出现线程安全问题的,而且线程还一直阻塞停止不了运行,我感觉您的本意是想实现和LinkedBlockingQueue一样的模式,两把锁,您测试时没出现问题过吗?
    2019-12-04
  • neohope
    看大家都提到了用令牌筒和MQ做限流和熔断。其实当并发量更大时,还有一种方式,但要牺牲一定的公平性。首先根据一些相对公平的规则,事先做一次资源分配。也就是对用户分组,预先分配一部分资源,每组用户抢夺组内资源。
    2019-11-24
  • 赤城
    请问老师,电商库存的并发问题是否可以使用CountDownLatch来实现呢,感觉实现起来更简单,毕竟库存只是一个数量,不必用一个数组来表示。
    2019-11-13
  • 2102
    增加消费者

    作者回复: 增加消费者是一种方式

    2019-10-19
  • 哲锄
    LinkedList 的 add 和 removeLast 方法都有可能操作 first 引用,存在线程安全问题吧?

    作者回复: LinkedList是非线程安全容器,存在线程安全问题的

    2019-09-23
  • 风轻扬
    lockInterruptibly()。老师,为啥要用这个API,不用lock。我查了一下,两者的区别是:前者侧重于中断,后者侧重于获取锁。这个地方,您是怎么考虑的呢?
    2019-09-15
  • godtrue
    课后思考及问题
    我们可以用生产者消费者模式来实现瞬时高并发的流量削峰,然而这样做虽然缓解了消费方的压力,但生产方则会因为瞬时高并发,而发生大量线程阻塞。面对这样的情况,你知道有什么方式可以优化线程阻塞所带来的性能问题吗?
    1:减少生产者的流量压力——限流
    2:视业务场景而定判断是否可以拒绝部分多余流量
    3:使用工业级消息队列中间件
    4:加缓存
    5:加机器
    2019-09-12
  • Geek_844248
    老师,优化ReentrantLock那里是不是有问题呢,product的修改放在两个不同的锁下,就是说可能会同时有两个线程会修改product这个list,这样是否违反了有序性。
    而且我尝试无限循环运行生产者消费者线程,发现运行久了会出错的,希望老师讲解一下。
    2019-08-12
  • K
    老师好,我有个问题,就是实际的inventory会不会超过maxInventory啊?

    productLock.lock();
    try {
    while (inventory.get() == maxInventory){ //3
    notFullCondition.await();
    }
    product.add(e); //1

    //producer被唤醒了以后,执行完1,还没执行2,这个时候时间片用完了,所以先停止了。
    //然后另外的线程被唤醒了,在3处的判断逻辑,(上一个线程并没有inventory.incr(),所以while条件不满足,不循环)
    //线程2号执行完代码1,2。
    //当之前一个线程1号醒过来,他也会继续执行代码2。这不是相当于,实际的inventory 肯定超过了maxInventory吗?

    System.out.println(" 放入一个商品库存,总库存为:" + inventory.incrementAndGet()); //2

    //后面的逻辑
    ...

    作者回复: 这有一个lock锁,不会同时进来两个线程。

    2019-08-11
  • 怎☞劰☜叻
    老师,我看到你上面说用协程来优化!我们这边有个服务属于业务网关,要聚合多个下有的数据,涉及大量的网络io,之前是使用多线程并行调用多个下有,现在发现线程越来越多,遇到了瓶颈!希望用协程来改进方案~但是我在网上找到的一些java协程开源组件,文档和生态都不是很健全,希望老师能给出一些建议~ 非常感谢

    作者回复: 建议再等等官方的协成组件,或改用go实现,目前Java的一些第三方开源组件的生产环境的实践以及性能验证有待考验,如果不介意当小白鼠,也可以试试这些第三方协成组件。

    2019-08-09
  • Aaron
    商品从数据库压入redis
    缓存。
    同时库存压入redis,用商品ID作为key,用list模拟队列【1001,1001,1001】用商品🆔做队列元素,100件库存,那就存100个🆔在队列中,扣库存的时候,判断队列大小,可以防止超卖。所有都是靠redis的单线程原子操作保证,可行不
    2019-08-03
    1
  • 晓杰
    请问老师在分布式架构中,使用lock和blockqueue实现生产者消费者是不是不适用了

    作者回复: 是的,可以基于消息队列或redis缓存来实现。

    2019-07-31
  • JasonK
    你好,刘老师,最近生产上一个服务,老是半夜cpu飙升,导致服务死掉,排查问题看了下,都是GC task thread#15 (ParallelGC) 线程占用CPU资源,这是为什么?而且同样的服务我布了两台机器,一台服务会死掉,一台不会。请老师解惑。

    作者回复: 导致CPU飙升只是一个性能的直接表现,是不是有对象一直在创建,所以导致一直在GC。建议打开dump日志查看具体的内存使用情况以及对象的创建分布情况。

    2019-07-31
    2
  • Jxin
    1.生产消费模式用信号量也能玩。
    2.生产者这边的优化思路应该是提高响应速度和增加资源。提高响应速度就是尽量降低生产逻辑的耗时,增加资源就是根据业务量为该生产者单独线程池并调整线程数。至于限流和令牌桶感觉都是降级处理,属于规避阻塞场景而非解决阻塞场景,应该不在答案范围内吧。
    3.对于进程内生产消费模式,大规模,量大的数据本身就不适合,毕竟内存空间有限,消息堆积有限,所以量级达到一定指标就采用跨进程方式,比如kafka和rocketmq。同时,进程内生产消费模式,异常要处理好,不然可能会出现消息堆积和脏数据,毕竟mq的消费确认和重试机制都是开箱即用,而我们得自己实现和把关。

    作者回复: 看来有实战经验👍🏻

    2019-07-31
  • 我已经设置了昵称
    kafka也有事务消息

    作者回复: 是的

    2019-07-31
    1
  • nightmare
    可以在网关通过令牌桶算法限流,真正执行的生产者一方使用线程池来优化

    作者回复: 限流是一种方式,线程池其实也是一种限流手段。我们在之前协程这一讲中,其实也用协程代替线程实现了生产者消费者模式,这也不乏是一种优化方式。

    2019-07-30
  • 明翼
    老师,生产者和消费者的锁分开没问题吗?都是用的同一个队列?

    作者回复: 这里同步更新下,新增了以下代码作为实时库存:
    private AtomicInteger inventory = new AtomicInteger(0);

    我们这里是基于LinkedList来存取库存的,虽然LinkedList是非线程安全,但我们新增是操作头部,而消费则是操作队列的尾部,理论上来说没有线程安全问题。而库存的实际数量inventory是基于AtomicInteger(CAS锁)线程安全类实现,即可以保证原子性,也可以保证消费者和生产者之间是可见的。

    2019-07-30
收起评论
27
返回
顶部