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

12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法

刘超 2019-06-15
你好,我是刘超。从这讲开始,我们就正式进入到第三模块——多线程性能调优。
在并发编程中,多个线程访问同一个共享资源时,我们必须考虑如何维护数据的原子性。在 JDK1.5 之前,Java 是依靠 Synchronized 关键字实现锁功能来做到这点的。Synchronized 是 JVM 实现的一种内置锁,锁的获取和释放是由 JVM 隐式实现。
到了 JDK1.5 版本,并发包中新增了 Lock 接口来实现锁功能,它提供了与 Synchronized 关键字类似的同步功能,只是在使用时需要显示获取和释放锁。
Lock 同步锁是基于 Java 实现的,而 Synchronized 是基于底层操作系统的 Mutex Lock 实现的,每次获取和释放锁操作都会带来用户态和内核态的切换,从而增加系统性能开销。因此,在锁竞争激烈的情况下,Synchronized 同步锁在性能上就表现得非常糟糕,它也常被大家称为重量级锁。
特别是在单个线程重复申请锁的情况下,JDK1.5 版本的 Synchronized 锁性能要比 Lock 的性能差很多。例如,在 Dubbo 基于 Netty 实现的通信中,消费端向服务端通信之后,由于接收返回消息是异步,所以需要一个线程轮询监听返回信息。而在接收消息时,就需要用到锁来确保 request session 的原子性。如果我们这里使用 Synchronized 同步锁,那么每当同一个线程请求锁资源时,都会发生一次用户态和内核态的切换。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(62)

  • bro.
    Synchronized锁升级步骤

    1. 偏向锁:JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能 ,
    2. 偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁
    3. 当锁对象第一次被线程获取的时候,线程使用CAS操作把这个锁的线程ID记录再对象Mark Word之中,同时置偏向标志位1。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。
    4. 如果线程使用CAS操作时失败则表示该锁对象上存在竞争并且这个时候另外一个线程获得偏向锁的所有权。当到达全局安全点(safepoint,这个时间点上没有正在执行的字节码)时获得偏向锁的线程被挂起,膨胀为轻量级锁(涉及Monitor Record,Lock Record相关操作,这里不展开),同时被撤销偏向锁的线程继续往下执行同步代码。
    5. 当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束
    6. 线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录(Lock Record)的空间,并将对象头中的Mard Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。如果自旋失败则锁会膨胀成重量级锁。如果自旋成功则依然处于轻量级锁的状态
    7. 轻量级锁的解锁过程也是通过CAS操作来进行的,如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中赋值的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了,如果替换失败,就说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程
    8. 轻量级锁提升程序同步性能的依据是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的(区别于偏向锁)。这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢
    简单概括为:
    1. 检测Mark Word里面是不是当前线程ID,如果是,表示当前线程处于偏向锁
    2. 如果不是,则使用CAS将当前线程ID替换到Mark Word,如果成功则表示当前线程获得偏向锁,设置偏向标志位1
    3. 如果失败,则说明发生了竞争,撤销偏向锁,升级为轻量级锁
    4. 当前线程使用CAS将对象头的mark Word锁标记位替换为锁记录指针,如果成功,当前线程获得锁
    5. 如果失败,表示其他线程竞争锁,当前线程尝试通过自旋获取锁 for(;;)
    6. 如果自旋成功则依然处于轻量级状态
    7. 如果自旋失败,升级为重量级锁
       - 索指针:在当前线程的栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象的标记字段复制到改锁记录中!

    作者回复: 赞

    2019-06-18
    2
    32
  • 而立朱先生
    很少评论,但今天看的这个mark word对象和锁升级的图画的真是倍儿棒,一目了然,目前看过synchronized锁机制中最好理解的
    2019-06-19
    10
  • nightmare
    加在普通方法锁对象是当前对象,其ObjectMonitor就是对象的,而静态方法上,锁对象就是字节码对象,静态方法是所有对象共享的,锁粒度比较大
    2019-06-15
    7
  • 苏志辉
    entrylist和waitset那个地方不太理解,monitorenter失败后会进入entrylist吧,只有调用wait方法才会进入waitset吧,还请老师指点下

    作者回复: 在获取到参与锁资源竞争的线程会进入entrylist,线程monitorenter失败后会进入到waitset,此时说明已经有线程获取到锁了,所以需要进入等待。调用wait方法也会进入到waitset。

    2019-06-16
    4
  • 陆离
    非静态方法是对象锁,静态方法是类锁
    2019-06-15
    4
  • chris~jiang
    老师,您好,synchronized锁只会升级,不会降级吧?如果系统只在某段时间高并发,升级到了重量级锁,然后系统变成低并发了,就一直是重量级锁了吗?请老师解惑,谢谢🙏

    作者回复: 锁状态只能升级不能降级。

    2019-07-02
    3
  • 不靠谱~
    1.课后作业:实际对象锁和类对象锁的区别,锁对象不一样。
    2. 1.8后CurrentHashmap已经不用segment策略了,想请教一下老师1.8后是怎样保证性能的呢?
    3.对锁升级不太了解的同学可以看一下《Java并发编程的艺术》。里面有很详细的介绍,不过也是比较难理解,多看几遍。

    作者回复: JDK1.8之后ConcurrentHashMap就放弃了分段锁策略,而是直接使用CAS+Synchronized方式保证性能,这里的锁是指锁table的首个Node节点。在添加数据的时候,如果Node数组没有值的情况,则会使用CAS添加数据,CAS成功则添加成功,失败则进入锁代码块执行插入链表或红黑树或转红黑树操作。

    2019-06-20
    3
  • -W.LI-
    老师好!获取偏斜锁和轻量级锁的时候使用的CAS操作预期值传的是null(希望锁已释放),替换后值是当前线程什么?

    作者回复: 老师没有看懂你问的具体问题,麻烦再描述一下你的问题。

    2019-06-18
    3
  • 晓杰
    感觉讲得有点晦涩啊,不知道其他人什么感觉

    作者回复: 如果哪里不懂的,可以多提问,希望我能帮助到你。

    2019-06-16
    1
    3
  • 承香墨影
    老师,对我 waitSet 的理解也有歧义。
    按您的在留言中的说法以及本文的内容,那等于进入 waitSet 会有两种情况,竞争 Monitor 失败,以及调用了 wait() 方法。
    那何时会唤醒呢?
    竞争 Monitor 失败的线程会在之前线程退出 Monitor 的时候再去竞争 Monitor,但是因外 wait() 方法也会进入 waitSet 的线程,就需要等待有线程退出的时候调用 notify() 方法,这一部分的细节和数据转换是怎么一回事?如何保证两种情况进入 waitSet 的线程,都拥有再次竞争 Monitor 的权利?

    作者回复: 这里老师纠正下,当竞争Monitor失败后,是去到ContentionList队列,而运行中的线程调用了wait方法会进入到WaitSet队列,等调用notify方法,会去队列中唤醒相应的线程,进入到EntryList队列中。文中已更新。

    2019-10-09
    1
    2
  • godtrue
    课后思考及问题
    首先,给老师的画图点个赞
    这个太重要了,老师讲的相当棒,不过还是有些东西未消化,所以,特意多刷几遍。
    再刷新时有如下疑问:
    1:Mark Word 在 64 位 JVM 中的长度是 64bit,老师给出的图我计算了几次都不到64bit,是配图有问题嘛?另外,31bit21bit未使用和下面的54bit及2bit,那块没懂是什么意思?
    2:在高并发场景下,当大量线程同时竞争同一个锁资源时,偏向锁就会被撤销,发生 stop the word 后, 开启偏向锁无疑会带来更大的性能开销。
    为啥发生 stop the word ?
     开启偏向锁无疑会带来更大的性能开销,这个怎么理解,是因为发生了 stop the word,所以,才带来更大的性能开销还是别的什么带来的更大的性能开销?
    3:JIT 编译器在动态编译同步块的时候,借助了一种被称为逃逸分析的技术,来判断同步块使用的锁对象是否只能够被一个线程访问,而没有被发布到其它线程。
    确认是的话,那么 JIT 编译器在编译这个同步块的时候不会生成 synchronized 所表示的锁的申请与释放的机器码,即消除了锁的使用。
    老师逃逸分析不太理解是啥意思?能否稍微再讲解一下?
    4:锁粗化同理,就是在 JIT 编译器动态编译时,如果发现几个相邻的同步块使用的是同一个锁实例,那么 JIT 编译器将会把这几个同步块合并为一个大的同步块,从而避免一个线程“反复申请、释放同一个锁“所带来的性能开销。
    发现第一次理解的锁粗化是错误的,锁粗化——粗化主要体现在将同一个锁实例相邻的同步代码快合并到了一起,使同步代码块的粒度变大了,不过减少了反复申请和释放同一个锁所带来的性能开销。
    2019-09-09
    2
  • bro.
    修饰普通方法是改类的对象,比如class A 创建了两个对象 class A1 跟class A2,对于method1来说A1,A2直接不是互斥的,但是对于静态方法或者synchronized(A.class)表示加锁对象为.class文件,一个项目只有唯一一个class文件,所以是互斥的
    2019-06-18
    2
  • Jxin
    1.8后,可以尽量采用并发包中的无锁或则称乐观锁来实现。读写极端场景可以看情况选用读写锁或票据锁。
    课后题,前者锁实例,后者锁类的字节码对象。后者力度太大应该结合业务场景尽量规避。
    2019-06-15
    2
  • Young
    请问老师,如果取消自旋,那轻量级锁和重量级锁还有什么区别吗

    作者回复: 主要区别是获取锁的方式,如果没有自旋,轻量级锁是通过cas来获取锁的,cas失败则直接升级为重量级锁。

    2019-09-16
    1
  • 张海鹏
    老师,您文中提到的锁消除一块没有十分理解,意思是若只有一个线程正在使用同步块,synchronized关键字就不被编译,就不加锁,当有新的线程也调用这个代码块的时候再加锁,是这样么?另外这个“借助了一种被称为逃逸分析的技术”可以扩展讲解一下么?

    作者回复: 不是的,一旦锁消除了,就不会再使用该锁了。逃逸分析一般是对一个对象的作用域的分析,例如一个对象只能被一个线程访问到时,则会消除锁。

    2019-09-06
    1
  • SDL
    默默问句怎么减少锁竞争呢?

    作者回复: 减少锁占用时间和减小锁粒度是最直观的减少锁竞争的方式了

    2019-09-03
    1
    1
  • 任鹏斌
    普通方法中锁的是当前对象,静态方法锁的是静态类

    作者回复: 对的,普通方法中的锁时锁对象,而修饰静态方法是类锁

    2019-07-03
    1
  • 余冲
    老师感觉锁级别的优化,总结下来,就是为了防止线程因争取锁对象的monitor对象,而进入monitor的waitset队列中。因为monitor需要进行系统调用,发生用户态到内核态的切换。那
    2019-06-20
    1
  • 黑崽
    锁升级的图中显示markword是否存储线程ID的图中,两个路径是与不是,是不是画反了?是的话,要cas替换成自己。不是,那么就直接获取偏向锁

    作者回复: 是的,感谢黑崽同学的提醒。

    2019-06-17
    1
  • 龙疯疯
    锁优化真实讲得清晰
    2019-12-10
收起评论
62
返回
顶部