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性能调优实战
登录|注册

14 | 多线程之锁优化(下):使用乐观锁优化并行操作

刘超 2019-06-20
你好,我是刘超。
前两讲我们讨论了 Synchronized 和 Lock 实现的同步锁机制,这两种同步锁都属于悲观锁,是保护线程安全最直观的方式。
我们知道悲观锁在高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。那有没有可能实现一种非阻塞型的锁机制来保证线程的安全呢?答案是肯定的。今天我就带你学习下乐观锁的优化方法,看看怎么使用才能发挥它最大的价值。

什么是乐观锁

开始优化前,我们先来简单回顾下乐观锁的定义。
乐观锁,顾名思义,就是说在操作共享资源时,它总是抱着乐观的态度进行,它认为自己可以成功地完成操作。但实际上,当多个线程同时操作一个共享资源时,只有一个线程会成功,那么失败的线程呢?它们不会像悲观锁一样在操作系统中挂起,而仅仅是返回,并且系统允许失败的线程重试,也允许自动放弃退出操作。
所以,乐观锁相比悲观锁来说,不会带来死锁、饥饿等活性故障问题,线程间的相互影响也远远比悲观锁要小。更为重要的是,乐观锁没有因竞争造成的系统开销,所以在性能上也是更胜一筹。

乐观锁的实现原理

相信你对上面的内容是有一定的了解的,下面我们来看看乐观锁的实现原理,有助于我们从根本上总结优化方法。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(24)

  • 张学磊
    变量的原值为A,当线程T读取后到更新前这段时间,可能被其他线程更新为B值后又更新回A值,到当线程T进行CAS操作时感知不到这个变化,依然可以更新成功;StampdLock通过过去锁时返回一个时间戳可以解决该问题。

    作者回复: 不仅回答了问题,还给出了解决方案,赞一个

    2019-06-20
    15
  • crazypokerk
    LongAdder原理如下:将热点数据value被分离成多个单元的cell,每个cell独自维护内部的值,当前对象的实际值由cell[]数组中所有的cell累计合成。这样,热点就进行了有效的分离,提高了并行度,所以LongAdder虽然降低了并发竞争,但是却对实时更新的数据不友好。

    作者回复: 是的

    2019-06-20
    9
  • colin
    老师您好,cell数组里存数得是+1 -1这种操作值么?

    还有,“LongAdder 在操作后的返回值只是一个近似准确的数值,但是 LongAdder 最终返回的是一个准确的数值”这句话中“操作后返回值”和“最终返回值”怎么理解?

    作者回复: 假设操作后立即要获取到值,这个值可能是一个不准确的值。如果我们等待所有线程执行完成之后去获取,这个值肯定是准确的值。一般在做统计时,会经常用到这种操作,实时展现的只要求一个近似值,但最终的统计要求是准确的。

    2019-06-20
    7
  • QQ怪
    Longaddr还是不能理解,能否在举个简单点的例子理解吗?
    2019-06-20
    3
  • 风轻扬
    老师,ABA的问题,CAS最终关心的是:值是否是A。那ABA的影响是什么呢?

    作者回复: 我们假设一个队列来分析ABA问题,会更好理解。

    如果一个队列有A\B\A三个数据,在线程1获取队列头节点数据A后,如果CAS发现数据没有变,则修改头节点A为A1,此时正好有一个线程删除了头节点A,又有另外一个线程也删除了后来成为头节点的B,此时头节点是依然是A,而此时第一个线程去修改A,这将导致实际修改的不是队列刚开始的那个节点A。

    2019-08-25
    2
  • 左瞳
    根据你的测试结果,都是乐观锁最优,是不是线程变为100个或者以上,其他测试结果才会优于乐观锁?

    作者回复: 通常情况下,乐观锁的性能是要优于悲观锁,跟线程数量没有太大关系

    2019-06-26
    2
  • 陆离
    解决ABA可以利用一个版本号进行验证,每次更新,版本号都+1,同时满足版本号与旧值相同才更新
    2019-06-21
    2
  • Loubobooo
    一个变量V,如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段期间它的值曾经被改成了B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。
    2019-06-20
    1
  • 赤城
    ABA的问题使用自增版本号或者时间戳就可以解决
    2019-11-08
  • godtrue
    课后思考及问题
    1:本文核心观点
    1-1:乐观锁的实现核心——CAS,它包含了 3 个参数:V(需要更新的变量)、E(预期值)和 N(最新值)。
    只有当需要更新的变量等于预期值时,需要更新的变量才会被设置为最新值,如果更新值和预期值不同,则说明已经有其它线程更新了需要更新的变量,此时当前线程不做操作,返回 V 的真实值。
    1-2:通常情况下乐观锁的性能由于悲观锁,不过乐观锁有一定的使用场景,比如:它只能保证单个变量操作的原子性,当涉及到多个变量时,CAS 就无能为力,适用竞争不激烈的场景
    1-3:对于ABA问题,乐观锁解决不了
    1-4:ABA问题是指一个线程修改数据时,获取的最新值是A,在修改之前又有其他的线程将此值做了修改,比如:先改成了B后来又有线程将B改成了A,但是最早的那个线程是不知道的,还会修改成功的。老师在评论区给出了一个更通俗易懂的例子——如果一个队列有A\B\A三个数据,在线程1获取队列头节点数据A后,如果CAS发现数据没有变,则修改头节点A为A1,此时正好有一个线程删除了头节点A,又有另外一个线程也删除了后来成为头节点的B,此时头节点是依然是A,而此时第一个线程去修改A,这将导致实际修改的不是队列刚开始的那个节点A。
    1-5:ABA问题的解决思路
    1-5-1:StampdLock通过获取锁时返回一个时间戳可以解决
    1-5-2:通过添加版本号,版本号每次修改都增加一个版本,这样也能解决
    2019-09-09
  • 码农Kevin亮
    这里想反馈一下,每个小节都讲得太绕了,老师可否直接点题,我越看越困惑:
    1,关于“什么是乐观锁”。乐观锁=CAS?CAS不是属于无锁嘛,所以乐观锁=无锁?
    2,关于“CAS的实现原理”。CAS是通过锁缓存来实现的,是吗?而synchronized是锁总线,是吗?

    作者回复: 1、乐观锁是一种概念,通过版本号来实现锁是一种乐观锁,而CAS是一种乐观锁的具体实现;
    2、CAS是通过底层CPU缓存锁定实现的,这里的总线锁没有涉及到synchronized,只是之前老的CPU是根据总线锁来实现的,由于更新换代,目前使用的是性能更好的缓存锁。

    建议多阅读几次文章。

    2019-08-06
  • 文灏
    LongAdder 在操作后的返回值只是一个近似准确的数值, 但是 LongAdder 最终返回的是一个准确的数值. 那什么时候才能知道LongAdder现在返回的值是正确的了呢?

    作者回复: 例如,我们在做销量统计的时候,用到LongAdder 统计销量,我们只需要保证最终写入的销量,在以后查询是是准确的。具体的时间也许是毫秒之后能查到,也许是分钟之后,但我们只需要保证在写入之后,能最终统计之前写入的销量。

    2019-07-03
  • 很有帮助,系统性的重新审视学习各个锁,顺带将老师的测试代码用JMH测试框架、面向对象化重构了下。
    https://github.com/seasonsolt/lockTest,有助于自己进一步深度学习研究。

    作者回复: 赞

    2019-06-27
  • 左瞳
    说乐观锁会占用大量的cpu导致性能下降,如果不是线程数影响,那哪些场景下乐观锁效率会低于悲观锁?
    2019-06-26
  • z.l
    cas方法的三个参数是如何和cpu的缓存锁定机制联系到一起的呢?感觉没有理解,还请老师解答。

    作者回复: 原理就是,当某个处理器对缓存中的共享变量进行了操作,就会通知其它处理器放弃对存储该共享资源或者重新读取该共享资源。

    2019-06-23
  • 晓杰
    ABA问题指的是假设现在有一个变量count的值为A,线程T1在未读取count值之前,线程T2把count值改为B,线程T3又把count值改为A,当线程T1读取count发现值为A,虽然值还是A,但是已经被其他线程改变过。
    数值的原子递增可以不关注ABA问题,但是如果是原子化的更新对象,就需要关注ABA问题,因为有可能属性值发生了改变
    2019-06-21
  • slowChef
    如果从这个图看,LongAdder在几乎所有场景都远优于其他锁呀,是不是有问题呢?

    作者回复: 乐观锁的性能要优于悲观锁,这个没问题。但乐观锁并不是适合所有场景,所以很多时候还是需要使用到悲观锁。

    2019-06-21
  • 明翼
    关于最后的问题老师是不是可以通过版本号之类来控制,版本号只增加不减少是不是可以解决这个问题那

    作者回复: 是的

    2019-06-21
  • 明翼
    老师总线锁和缓存锁,这个缓存是l1还是L2还是L3那,总线锁可以理解成锁内存吗

    作者回复: 这三个等级的缓存都会涉及到,理解没问题。

    2019-06-21
  • WL
    请教老师两个问题:
    1. 文章中的这句话我不太理解, "我们可以发现,LongAdder 在操作后的返回值只是一个近似准确的数值,但是 LongAdder 最终返回的是一个准确的数值". 这么判断的依据是value的计算公式吗, 为什么value的计算公式可以保证最终返回的准确性, 公式中base和数组中各个槽的权重不一样, 为什么base有这么大的权重呢?
    2. 单核CPU是靠什么机制保证其他线程和进程都看到的缓存中的内容而不是内存中的内容呢?
    2019-06-20
收起评论
24
返回
顶部