【JUC】synchronized锁底层与锁升级策略
本文将记录学习到的synchronized底层实现以及Synchronized的锁升级的优化策略。
一、synchronized底层实现
synchronized是内置锁,使用时以关键字的方式进行使用。在使用synchronized后,底层编译后代码前后会被加上monitorenter和monitorexit字节码指令(无论偏向锁和轻量级锁都会进行这些字节码指令的执行)。
二、synchronized锁升级策略

锁升级的根本原因: synchronized最初只有重量级锁,依赖于操作系统的互斥锁(mutex),每次线程竞争都需要从用户态切换到内核态,性能开销很大。JDK 1.6开始引入锁升级优化,根据竞争情况动态调整锁状态。
1、偏向锁(Biased Locking)
偏向锁获取方式: 当锁从未被使用过时,这个状态叫做匿名偏向,若有一个线程A进行CAS获取后,锁对象的对象头MarkWord会记录这个线程的ID,然后获取锁成功,就是偏向锁,后续如果再次获取锁且中间没有其他线程获取锁,比对对象头MarkWord的线程ID后会直接获取成功。
偏向锁的升级: 当有其他线程B来获取偏向锁时(无论是在线程A使用或者是不使用状态),都会触发偏向锁撤销并升级成轻量级锁。若升级过程中线程A正在使用锁,JVM会找一个安全点(Safepoint)暂停线程A进行升级。若竞争特别激烈或检测到不适合偏向锁的场景,会直接升级成重量级锁。
- JDK 15开始默认禁用偏向锁(-XX:-UseBiasedLocking),因为维护成本高且实际收益有限
- 调用锁对象的
hashCode()方法会强制撤销偏向锁,因为偏向锁占用了对象头中hashCode的位置
2、轻量级锁(Thin Lock)
轻量级获取方式: 当锁是轻量级锁时,获取是通过CAS进行获取,线程在自己的栈帧中创建锁记录(Lock Record),先将对象头的MarkWord复制到线程的Lock Record中,然后通过CAS将对象头的MarkWord替换为指向Lock Record的指针,若成功就获取锁,反之进行自旋重试。
轻量级锁的升级: 若自旋过多(次数由JVM自适应调整)会直接升级成重量级锁。当有第三个线程加入竞争时会升级成重量级锁(两个线程竞争时仍保持轻量级锁状态)。
3、重量级锁(Heavyweight Lock)
重量级锁获取方式: 重量级锁的获取是,线程尝试CAS获取锁失败后,会先进行适应性自旋,若自旋失败则创建ObjectWaiter节点。线程不会直接进入entryList,而是先加入cxq队列(竞争队列),随后判断锁的_owner是否为null,若是则通过CAS尝试将_owner设置为当前线程,若不是则判断是否是自己,若是则重入,计数器++,反之线程挂起等待唤醒。
当锁释放时,JVM根据QMode参数从cxq或entryList中选择线程唤醒,被唤醒的线程仍需要CAS竞争锁才能获取成功。
在以下情况下会直接或快速升级到重量级锁:
- 调用wait/notify方法:只要代码中调用了
wait()、notify()或notifyAll()方法,锁会直接升级为重量级锁,因为这些方法需要ObjectMonitor支持。 - 调用hashCode()方法:在偏向锁或轻量级锁状态下调用对象的
hashCode()方法,会触发锁升级到重量级锁。 - 有线程在等待锁:当已经有线程在等待锁(进入阻塞状态)时,新来的线程会直接走重量级锁流程。
- 第三个线程加入竞争:轻量级锁状态下,当第三个线程尝试获取锁时,会直接升级为重量级锁。
- JVM参数强制设置:使用
-XX:-UseSpinning(禁用自旋)或-XX:-UseBiasedLocking(禁用偏向锁)等参数。
安全点(Safepoint): 安全点是JVM为了进行垃圾回收、偏向锁撤销等操作而选择的特殊位置。通常放置在方法调用、循环跳转、异常跳转等位置,这些位置的特点是线程状态确定,不会进行对象状态变化、引用修改等危险操作。当需要撤销偏向锁时,JVM会等待所有线程到达安全点后再执行,确保线程状态一致性。
🔄 锁升级流程总结
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
↑撤销偏向 ↑自旋失败
↑hashCode() ↑第三个线程竞争
↑wait/notify ↑长时间持有三、JVM对synchronized优化
1、锁升级:减少用户态与内核态之间的切换。
2、锁消除:若JVM监测到某段代码不会被争抢,就会消除锁
3、锁粗化:将多个加锁代码放到一个锁里面
4、自适应自旋,在重量级锁争抢时,会进行一小段的CAS自旋,避免直接切换内核态。