Java 性能调优实战
刘超
前金山软件技术经理
59174 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
开篇词 (1讲)
模块一 · 概述 (2讲)
结束语 (1讲)
Java 性能调优实战
15
15
1.0x
00:00/00:00
登录|注册

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

自旋锁的启用和关闭
适用于线程交替执行同步块的场景
撤销和关闭偏向锁
优化同一线程多次申请同一个锁的竞争
性能提升
特别是在单个线程重复申请锁的情况下
Synchronized同步锁性能糟糕
需要显式获取和释放锁
新增Lock接口
JVM隐式实现锁的获取和释放
依靠Synchronized关键字实现锁功能
减小锁的持有时间
减少锁竞争
逃逸分析
JIT编译器优化
自旋锁与重量级锁
轻量级锁
偏向锁
Monitor对象
字节码实现
修饰方法和方法块
JDK1.6优化
性能差异
JDK1.5之后的实现
JDK1.5之前的实现
减小锁粒度
动态编译实现锁消除/锁粗化
锁升级优化
Synchronized同步锁实现原理
Synchronized同步锁
多线程性能调优
思考题
并发编程
深入了解Synchronized同步锁的优化方法

该思维导图由 AI 生成,仅供参考

你好,我是刘超。从这讲开始,我们就正式进入到第三模块——多线程性能调优。
在并发编程中,多个线程访问同一个共享资源时,我们必须考虑如何维护数据的原子性。在 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/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Synchronized同步锁的优化方法是多线程性能调优中的重要内容。在并发编程中,Synchronized关键字是实现锁功能的一种方式,但在JDK1.5之前,它的性能表现并不理想。随着JDK1.6的优化,Synchronized同步锁的性能得到了提升,甚至在某些情况下超越了Lock同步锁。文章介绍了Synchronized同步锁的实现原理,包括修饰方法和修饰代码块的方式,以及锁升级优化的概念,包括偏向锁、轻量级锁和重量级锁。偏向锁主要用于优化同一线程多次申请同一个锁的竞争,而轻量级锁和重量级锁则是针对多线程竞争的情况。文章还介绍了Java对象头的结构和锁升级功能依赖于Mark Word中的锁标志位和释放偏向锁标志位。总的来说,Synchronized同步锁的优化方法涉及了底层实现原理和锁升级优化,对于理解并发编程中锁的性能优化具有重要意义。 在锁的优化方面,文章提到了轻量级锁、自旋锁和重量级锁的应用场景和优化效果。轻量级锁适用于短时间内持有锁且存在交替切换的场景,而自旋锁则通过自旋方式不断尝试获取锁,避免线程被挂起阻塞,提高系统性能。另外,文章还介绍了动态编译实现的锁消除和锁粗化,以及减小锁粒度的方法来降低锁竞争,提升并行度。最后,文章强调了减少锁竞争对于优化Synchronized同步锁的重要性,建议尽量使Synchronized同步锁处于轻量级锁或偏向锁状态,以提高性能。 总的来说,本文深入探讨了Synchronized同步锁的优化方法,涵盖了底层实现原理、锁升级优化和编译器优化等内容,为读者提供了全面的并发编程锁优化知识。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 性能调优实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(122)

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

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

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

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

    2019-10-09
    2
    14
  • 苏志辉
    entrylist和waitset那个地方不太理解,monitorenter失败后会进入entrylist吧,只有调用wait方法才会进入waitset吧,还请老师指点下

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

    2019-06-16
    7
    14
  • 天天向上
    老师在其他的回复中提到:synchronized锁只会升级,不会降级。如果系统只在某段时间高并发,升级到了重量级锁,然后系统变成低并发了,那还是重量锁,那岂不是很影响性能。

    作者回复: 不应该叫锁降级,只是在垃圾回收阶段,即STW时,没有Java线程竞争锁的情况下,会将锁状态重置。

    2020-04-19
    13
  • 浩瀚有边
    老师,您好,synchronized锁只会升级,不会降级吧?如果系统只在某段时间高并发,升级到了重量级锁,然后系统变成低并发了,就一直是重量级锁了吗?请老师解惑,谢谢🙏

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

    2019-07-02
    6
    13
  • 天天向上
    偏向锁的撤销需要等待全局安全点,暂停持有该锁的线程,同时检查该线程是否还在执行该方法。对此,有疑问,全局安全点指的是什么?什么情况下会出现暂停了该线程,该线程还在执行该方法?

    作者回复: JVM在编译代码为字节码时,在字节码的边界都可以放一个安全点(safepoint),从线程角度看,safepoint可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要,可以在这个位置暂停,暂停也就是我们说的发生stop-the-world(STW)。

    2020-04-19
    10
  • 张三丰
    entryList有序吗?感觉这个结果多余,sync没有实现公平锁。

    作者回复: 队列就是有序的,ContentionList会被线程并发访问,为了降低对ContentionList队尾的争用,而建立了EntryList。 sync的公平和非公平提现在进入ContentionList队列之前,有一个cas自旋获取锁操作,获取不到再进入队列。

    2020-04-08
    7
  • Wheat Liu
    老师您好,想问一下,偏向锁的撤销为什么要在SafePoint暂停该线程呢,是因为要改变锁对象的头信息吗,那在线程运行时撤销偏向锁会出现什么问题呢

    作者回复: 如果不暂停就不能正确判断线程是否正在持有偏向锁,暂停的目的是保证能正确判断线程持有偏向锁状态以及线程执行代码块的情况。

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

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

    2019-09-06
    3
    4
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部