参考小林codingHome
JavaGuideJava 面试指南
Synchronized原理
首先要介绍一下Object对象锁。在Java中,每个实例都有一个关联的Monitor锁。当线程进入synchronized代码块时,会尝试获取Object对应的Monitor。
使用monitorenter指向同步代码块开始的位置,monitorexit指向同步代码块结束的位置
两个monitorexit,一个对应正常退出,一个对应异常退出。
JVM执行monitorenter指令时,会尝试获取对象的监视器锁。如果锁是空闲的,那么线程获取锁,继续执行。如果锁已被其他线程占有,那么线程阻塞,等待锁释放。
monitor底层基于操作系统的互斥量来实现
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取而代之的是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。如果是实例方法,JVM 会尝试获取实例对象的锁。如果是静态方法,JVM 会尝试获取当前 class 的锁。
构造方法是否可以用synchronized修饰
构造方法不能使用 synchronized 关键字修饰。不过,可以在构造方法内部使用 synchronized 代码块。
另外,构造方法本身是线程安全的,但如果在构造方法中涉及到共享资源的操作,就需要采取适当的同步措施来保证整个构造过程的线程安全。
synchronized锁实例和锁类的class对象
参考Java synchronized
机制详解:实例锁与类锁,普通方法锁与静态方法锁的区别 - Astrylia小站
synchronized是可重入锁
synchronized 是一种可重入锁,意思是同一个线程在持有某个对象的锁之后,可以再次获取该对象的锁而不会被阻塞。在 JVM 中,每个对象都关联一个 Monitor 监视器。当一个线程第一次获取锁时,JVM 会将该线程记录为持有者,并将重入计数器设置为 1。 如果该线程再次进入同一个 synchronized 方法或代码块,JVM 会检测到它已经是锁的持有者,于是允许重入,并将计数器加 1。 当该线程退出同步块时,计数器减 1,只有当计数器为 0 时,锁才会真正释放,其他线程才有机会获取这个锁。
synchronized锁升级
1. 无锁(No Lock)
对象刚创建时,没有被加锁。
2. 偏向锁(Biased Lock)
目的:优化 单线程重复加锁 的情况
- 对象第一次被某个线程获取后,JVM 在对象头(Mark Word)中记录这个线程的 ID。
- 同一个线程再次进入同步块时,只需比对线程 ID 是否一致,就可以直接进入,无需 CAS(高性能)。
- 如果其他线程尝试获取锁 → 偏向锁会被撤销(撤销过程需要安全点、可能有 STW),进入下一阶段。
3. 轻量级锁(Lightweight Lock)
目的:优化 短时间内多线程交替加锁但无竞争 的情况
- 当第二个线程尝试获取偏向锁时,JVM 会尝试使用 CAS 机制尝试加锁。
- 若 CAS 成功,就会变成轻量级锁。
- 在轻量级锁下,如果没有竞争,线程依然可以非常快地获取和释放锁。
如果 CAS 失败,说明出现了真实竞争,进入下一阶段。
4. 重量级锁(Heavyweight Lock)
目的:处理多个线程同时竞争锁的场景
- JVM 将该对象的锁升级为重量级锁,涉及 Monitor(监视器)。
- 被阻塞的线程会挂起(进入等待队列),等待锁释放。
- 锁的获取与释放需要线程阻塞/唤醒,这是一种昂贵的系统调用(涉及上下文切换)。
锁的升级是单向的、不可降级的!
一旦进入更高级别的锁状态,不会降级为偏向锁或轻量级锁。
synchronized锁优化
- 锁膨胀:synchronized 从无锁升级到偏向锁,再到轻量级锁,最后到重量级锁的过程
- 锁消除:指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。
- 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。