Java synchronized 底层原理

Java 中的 synchronized 关键字用于实现线程之间的同步,保证共享资源在多线程环境下被安全地访问。其底层原理涉及多个步骤和机制,依赖于 JVM 内置的锁机制和对象头结构。这里是它的底层工作原理及锁的相关细节:

1. Java 对象头(Object Header)和 Mark Word

每个 Java 对象都包含一个对象头(Object Header),其中存储了锁的信息。Java 对象头包含两个主要部分:

  • Mark Word:存储了锁状态、GC 标记、哈希码等信息。Mark Word 的大小为 32 位或 64 位,取决于 JVM 的位数。
  • Class Pointer:指向对象的类元数据。

在未加锁状态下,Mark Word 包含对象的哈希值和 GC 标记信息。当一个线程对该对象加锁时,Mark Word 被修改以保存线程持有锁的信息。

2. 锁的状态

Mark Word 中记录了锁的状态,锁的状态在运行时是可以升级的,主要有以下几种:

  • 偏向锁(Biased Locking):线程第一次加锁时将锁偏向该线程,如果再次加锁且没有其他线程争用时,锁依然保持偏向状态,无需做额外操作,开销极小。偏向锁使用了 Mark Word 中线程 ID 来记录持有锁的线程。
  • 轻量级锁(Lightweight Locking):如果有多个线程争用,锁会升级为轻量级锁。轻量级锁使用 CAS(Compare-And-Swap)操作来尝试获取锁,通过自旋等待方式避免线程阻塞。
  • 重量级锁(Heavyweight Locking):当争用激烈且轻量级锁自旋失败时,锁会升级为重量级锁,此时线程会进入阻塞状态,重量级锁使用操作系统的互斥量(Mutex)机制。

3. 锁的获取与释放流程

  • 加锁(锁的获取)
    1. 无锁状态:当线程首次尝试获取锁时,JVM 会将对象头的 Mark Word 设置为偏向锁的状态,并将持有锁的线程 ID 写入 Mark Word 中。
    2. 轻量级锁:如果偏向锁失败(有多个线程竞争锁),锁会升级为轻量级锁。JVM 为当前线程创建一个栈帧中的锁记录,将对象头的 Mark Word 拷贝到锁记录中,然后尝试用 CAS 操作将对象头指向锁记录。
    3. 重量级锁:当轻量级锁的 CAS 自旋操作多次失败时,锁会升级为重量级锁。此时,线程会被阻塞,直到锁被释放。
  • 解锁(锁的释放)
    1. 线程持有锁时,当退出同步代码块,JVM 会根据锁的当前状态,恢复对象头的 Mark Word。偏向锁和轻量级锁时,会将锁信息恢复到解锁前的状态。
    2. 如果锁升级为重量级锁,解锁时会唤醒被阻塞的线程。

4. 锁的状态标志(Mark Word 内容)

Java 中 Mark Word 的锁状态可以通过其中的几个位来标志不同状态:

  • 无锁状态:标志位为 01,表示对象头未被锁。
  • 偏向锁:标志位为 101,表示锁偏向某个线程。
  • 轻量级锁:标志位为 00,表示处于轻量级锁状态。
  • 重量级锁:标志位为 10,表示处于重量级锁状态。

5. 线程如何知道对象被锁住了?

当一个线程尝试获取锁时,会检查对象头中的 Mark Word。如果发现对象头中的标志位已经被修改(例如,存在另一个线程持有偏向锁或轻量级锁),线程会通过 CAS 操作竞争锁,并根据不同的锁状态进行升级或阻塞。

6. 指令层面:MonitorEnter 与 MonitorExit

synchronized 关键字在字节码层面会转换为两条指令:

  • MonitorEnter:表示线程试图进入同步块并获取锁。
  • MonitorExit:表示线程释放锁。

这两条指令与 JVM 内部的对象头和 Mark Word 状态紧密相关,JVM 会使用这些指令来进行加锁和解锁操作。

总结:

  • 锁的获取过程synchronized 使用对象的 Mark Word 来记录锁的状态,通过偏向锁、轻量级锁和重量级锁的升级来优化性能。
  • 锁的同步机制:线程通过 MonitorEnterMonitorExit 指令实现同步操作,并通过 CAS 自旋、锁状态标志以及系统调用等机制来协调多线程访问共享资源。

这种分级锁策略极大地提高了 Java 的并发性能,减少了线程竞争时的上下文切换开销。

0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x