Synchronized 锁升级优化

来源:阿里巴巴java性能调优实战.pdf

锁升级优化

为了提升性能,JDK1.6 引入了偏向锁、轻量级锁、重量级锁概念,来减少锁竞争带来的上下文切换,而正是新增的 Java 对象头实现了锁升级功能。 当 Java 对象被 Synchronized 关键字修饰成为同步锁后,围绕这个锁的一系列升级操作都 将和 Java 对象头有关。

Java 对象头

在 JDK1.6 JVM 中,对象实例在堆内存中被分为了三个部分:对象头、实例数据和对齐填充。其中 Java 对象头由 Mark Word、指向类的指针以及数组长度三部分组成。 Mark Word 记录了对象和锁有关的信息。Mark Word 在 64 位 JVM 中的长度是 64bit, 我们可以一起看下 64 位 JVM 的存储结构是怎么样的。如下图所示:
image.png
锁升级功能主要依赖于 Mark Word 中的锁标志位和是否偏向锁标志位,Synchronized 同步锁就是从偏向锁开始的,随着竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。下面我们就沿着这条优化路径去看下具体的内容。

偏向锁

偏向锁主要用来优化同一线程多次申请同一个锁的竞争。在某些情况下,大部分时间是同一个线程竞争锁资源,例如,在创建一个线程并在线程中执行循环监听的场景下,或单线程操 作一个线程安全集合时,同一线程每次都需要获取和释放锁,每次操作都会发生用户态与内核态的切换。

偏向锁的作用就是,当一个线程再次访问这个同步代码或方法时,该线程只需去对象头的 Mark Word 中去判断一下是否有偏向锁指向它的 ID,无需再进入 Monitor 去竞争对象 了。**当对象被当做同步锁并有一个线程抢到了锁时,锁标志位还是 01,“是否偏向锁”标 志位设置为 1,并且记录抢到锁的线程 ID,表示进入偏向锁状态。 **
一旦出现其它线程竞争锁资源时,偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点, 暂停持有该锁的线程,同时检查该线程是否还在执行该方法,如果是,则升级锁,反之则被其它线程抢占。
下图中红线流程部分为偏向锁获取和撤销流程:
image.png
因此,在高并发场景下,当大量线程同时竞争同一个锁资源时,偏向锁就会被撤销,发生 stop the word 后, 开启偏向锁无疑会带来更大的性能开销,这时我们可以通过添加 JVM 参数关闭偏向锁来调优系统性能,示例代码如下:
:::info
-XX:-UseBiasedLocking // 关闭偏向锁(默认打开)
:::

:::info
-XX:+UseHeavyMonitors // 设置重量级锁
:::

轻量级锁

当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头 Mark Word
中的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁,如果获取成功,直接替换
Mark Word 中的线程 ID 为自己的 ID,该锁会保持偏向锁状态;如果获取锁失败,代表当
前锁有一定的竞争,偏向锁将升级为轻量级锁。
轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时
间的竞争。下图中红线流程部分为升级轻量级锁及操作流程:
image.png
**自旋锁与重量级锁 **
轻量级锁 CAS 抢锁失败,线程将会被挂起进入阻塞状态。如果正在持有锁的线程在很短的时间内释放资源,那么进入阻塞状态的线程无疑又要申请锁资源。

JVM 提供了一种自旋锁,可以通过自旋方式不断尝试获取锁,从而避免线程被挂起阻塞。 这是基于大多数情况下,线程持有锁的时间都不会太长,毕竟线程被挂起阻塞可能会得不偿失。

从 JDK1.7 开始,自旋锁默认启用,自旋次数由 JVM 设置决定,这里我不建议设置的重试 次数过多,因为 CAS 重试操作意味着长时间地占用 CPU。

自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁,锁标志位改为 10。在这 个状态下,未抢到锁的线程都会进入 Monitor,之后会被阻塞在 _WaitSet 队列中。
下图中红线流程部分为自旋后升级为重量级锁的流程:
image.png
在锁竞争不激烈且锁占用时间非常短的场景下,自旋锁可以提高系统性能。一旦锁竞争激烈
或锁占用的时间过长,自旋锁将会导致大量的线程一直处于 CAS 重试状态,占用 CPU 资
源,反而会增加系统性能开销。所以自旋锁和重量级锁的使用都要结合实际场景。
在高负载、高并发的场景下,我们可以通过设置 JVM 参数来关闭自旋锁,优化系统性能,
示例代码如下:
:::info
-XX:-UseSpinning // 参数关闭自旋锁优化 (默认打开)
-XX:PreBlockSpin // 参数修改默认的自旋次数。JDK1.7 后,去掉此参数,由 jvm 控制
:::

动态编译实现锁消除 / 锁粗化

除了锁升级优化,Java 还使用了编译器对锁进行优化。JIT 编译器在动态编译同步块的时候,借助了一种被称为逃逸分析的技术,来判断同步块使用的锁对象是否只能够被一个线程访问,而没有被发布到其它线程。

确认是的话,那么 JIT 编译器在编译这个同步块的时候不会生成 synchronized 所表示的锁的申请与释放的机器码,即消除了锁的使用。在 Java7 之后的版本就不需要手动配置了,该操作可以自动实现。

锁粗化同理,就是在 JIT 编译器动态编译时,如果发现几个相邻的同步块使用的是同一个锁 实例,那么 JIT 编译器将会把这几个同步块合并为一个大的同步块,从而避免一个线程“反复申请、释放同一个锁“所带来的性能开销。

减小锁粒度

除了锁内部优化和编译器优化之外,我们还可以通过代码层来实现锁优化,减小锁粒度就是 一种惯用的方法。
当我们的锁对象是一个数组或队列时,集中竞争一个对象的话会非常激烈,锁也会升级为重 量级锁。我们可以考虑将一个数组和队列对象拆成多个小对象,来降低锁竞争,提升并行 度。
最经典的减小锁粒度的案例就是 JDK1.8 之前实现的 ConcurrentHashMap 版本。我们知 道,HashTable 是基于一个数组 + 链表实现的,所以在并发读写操作集合时,存在激烈的 锁资源竞争,也因此性能会存在瓶颈。而 ConcurrentHashMap 就很很巧妙地使用了分段锁 Segment 来降低锁资源竞争,如下图所示:
image.png

总结

JVM 在 JDK1.6 中引入了分级锁机制来优化 Synchronized,当一个线程获取锁时,首先对象锁将成为一个偏向锁,这样做是为了优化同一线程重复获取导致的用户态与内核态的切换问题;其次如果有多个线程竞争锁资源,锁将会升级为轻量级锁,它适用于在短时间内持有锁,且分锁有交替切换的场景;偏向锁还使用了自旋锁来避免线程用户态与内核态的频繁切换,大大地提高了系统性能;但如果锁竞争太激烈了,那么同步锁将会升级为重量级锁。

减少锁竞争,是优化 Synchronized 同步锁的关键。我们应该尽量使 Synchronized 同步锁处于轻量级锁或偏向锁,这样才能提高 Synchronized 同步锁的性能;通过减小锁粒度 来降低锁竞争也是一种最常用的优化方法;另外我们还可以通过减少锁的持有时间来提高 Synchronized 同步锁在自旋时获取锁资源的成功率,避免 Synchronized同步锁升级为重量级锁。

  • 23
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java中的synchronized锁升级是为了提高多线程并发执行的性能和效率。它通过在锁的使用过程中进行优化和升级来实现。 在Java中,synchronized锁的升级主要涉及三个层面:Java层面、字节码层面和JVM层面(对象头)。 在Java层面上,synchronized锁的升级包括以下几种状态: 1. 无锁状态:多个线程可以同时进入临界区,没有互斥的限制; 2. 偏向锁状态:当只有一个线程访问临界区时,偏向锁可以减少锁的竞争; 3. 轻量级锁状态:多个线程竞争同一个锁时,锁会升级为轻量级锁,通过CAS操作来实现快速的加锁和解锁; 4. 重量级锁状态:多个线程竞争同一个锁时,锁会升级为重量级锁,使用操作系统的互斥量来实现线程的阻塞和唤醒[1]。 在字节码层面上,synchronized同步代码块的锁升级实际上是通过字节码指令来实现的。当进入同步代码块时,会通过monitorenter指令获取锁,在退出同步代码块时,会通过monitorexit指令释放锁。这些指令可以保证临界区的原子性和互斥性,从而实现线程的同步。 在JVM层面上,synchronized锁的升级是通过对象头中的标记位来实现的。对象头中的标记位包括了锁标志位、线程ID和指向锁记录的指针。通过这些标记位,JVM可以判断锁的状态和竞争情况,从而进行锁的升级和降级。 总结来说,Java中的synchronized锁升级是为了提高多线程并发执行的性能和效率。它通过在不同层面上对锁进行优化和升级来实现线程的同步和互斥,从而保证临界区的原子性和正确性。这些优化和升级包括了无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值