MST

星途 面试题库

面试题:Java并发编程中,synchronized锁的实现原理是什么?

请详细阐述Java中synchronized关键字加锁的底层机制,比如在对象头中是如何体现锁状态的,以及锁的升级过程。
21.0万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java中synchronized关键字加锁的底层机制

  1. 对象头:在HotSpot虚拟机中,对象在内存中的布局分为对象头、实例数据和对齐填充三部分。对象头又包含两部分信息,一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、对象分代年龄等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称它为“Mark Word”;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  2. 锁状态在对象头中的体现

    • 无锁状态:在无锁状态下,Mark Word的低位存储对象的HashCode、分代年龄、是否偏向锁标志位以及锁标志位。其中锁标志位为01,偏向锁标志位为0。
    • 偏向锁状态:当对象第一次被线程访问时,会在Mark Word中记录当前线程的ID,偏向锁标志位为1,锁标志位依然为01。后续该线程再次访问此对象时,只需要对比Mark Word中的线程ID是否与当前线程ID一致,一致则无需进行额外的同步操作。
    • 轻量级锁状态:当有第二个线程尝试获取偏向锁时,偏向锁会升级为轻量级锁。此时Mark Word中的锁标志位变为00。在轻量级锁状态下,JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),用于存储对象头的Mark Word的拷贝,然后尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果替换成功,则当前线程获得轻量级锁;如果失败,表示存在竞争,轻量级锁会膨胀为重量级锁。
    • 重量级锁状态:当轻量级锁竞争失败后,锁会升级为重量级锁,此时Mark Word中的锁标志位变为10。重量级锁依赖操作系统的互斥量(Mutex)来实现同步,线程获取锁失败时会进入阻塞状态,等待操作系统的调度。
  3. 锁的升级过程

    • 偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问加了synchronized关键字的代码块时,首先会检查对象头中的偏向锁标志位。如果偏向锁标志位为0(即对象处于无锁状态),且对象头中的线程ID为空,那么当前线程会通过CAS操作将自己的线程ID记录到对象头中,并将偏向锁标志位设置为1,此时对象进入偏向锁状态。如果偏向锁标志位为1,且对象头中的线程ID与当前线程ID一致,那么当前线程可以直接进入同步代码块,无需进行其他同步操作。
    • 轻量级锁:当有第二个线程尝试获取偏向锁时,发现对象头中的线程ID与自己不一致,偏向锁就会升级为轻量级锁。JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),用于存储对象头的Mark Word的拷贝,然后尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果替换成功,则当前线程获得轻量级锁;如果失败,表示存在竞争,轻量级锁会膨胀为重量级锁。
    • 重量级锁:当轻量级锁竞争失败后,锁会升级为重量级锁。此时,所有竞争锁的线程都会被阻塞,放入一个等待队列中,由操作系统来调度这些线程。重量级锁的开销比较大,因为线程的阻塞和唤醒都需要操作系统的参与,涉及用户态到内核态的切换。

这种锁升级的机制,使得Java在不同的竞争场景下能够选择最合适的锁机制,从而提高程序的性能。在竞争不激烈的情况下,偏向锁和轻量级锁能够减少线程获取锁的开销;而在竞争激烈的情况下,重量级锁虽然开销大,但能保证数据的一致性和线程安全。