欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > Java synchronized底层原理深度解析

Java synchronized底层原理深度解析

2025/2/24 8:25:18 来源:https://blog.csdn.net/Gaomengsuanjia_/article/details/145742347  浏览:    关键词:Java synchronized底层原理深度解析

Java synchronized底层原理深度解析


在Java中,synchronized是用于实现线程同步的重要关键字,它的主要作用是控制访问共享资源的线程的数量。在并发编程中,理解synchronized底层是非常重要的,它不仅能帮助我们避免死锁和竞态条件,还能帮助我们优化程序性能。在本篇博客中,我们将深入探讨synchronized底层的实现原理,涵盖以下几个主要概念:对象头、Monitor、偏向锁、轻量级锁、重量级锁、锁膨胀以及自旋优化等

我们都知道,synchronized`是用于实现线程同步的重要关键字,防止出现线程安全问题,我们俗称他为锁:

synchronized(//锁对象) {// 临界区
}

这里的obj就是锁对象

那么现在我们将讨论他的加锁过程以及原理


对象头和Monitor

对象头

在Java中,所有的对象都有一个对象头(Object Header),它存储了与对象管理和同步相关的信息。对象头主要由两部分组成:Mark WordClass Pointer。Mark Word用于存储与锁相关的信息,而Class Pointer是指向标识该类类型的指针,两部分各占32位即8个字节

|--------------------------------------------------------------|
|                 Object Header (64 bits)                      |
|------------------------------------|-------------------------|
|       Mark Word (32 bits)          | Class Pointer (32 bits) |
|------------------------------------|-------------------------|

其中Mark Word有几种不同的状态,他们的结构为:

|-------------------------------------------------------|--------------------|
|                    Mark Word (32 bits)                |        State       |
|-------------------------------------------------------|--------------------|
| hashcode:25           | age:4 | biased_lock:0   | 01  |        Normal      |
|-------------------------------------------------------|--------------------|
| thread:23   | epoch:2 | age:4 | biased_lock:1   | 01  |        Biased      |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30             | 00  | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|              ptr_to_heavyweight_monitor:30      | 10  | Heavyweight Locked |
|-------------------------------------------------------|--------------------|

biased_lock用来表示是否是偏向锁状态,除此之外,各种状态可以用最后两位来区分

  • 00:表示轻量级锁
  • 10:表示重量级锁
  • 01:表示正常状态或者是偏向锁状态

那么这些锁都是什么呢,我们逐一讨论:


Monitor

Monitor是每个对象都有的同步机制,负责管理对象的锁。每个对象对应一个Monitor,这个Monitor不仅用来管理锁,还用于线程之间的协调。当一个线程试图获取某个对象的锁时,它需要与这个对象的Monitor进行交互。Monitor内部维护了锁的状态,具体锁的实现取决于不同的锁类型(偏向锁、轻量级锁、重量级锁等)

他的结构如下:

Monitor
WaitSet
EntryList
Owner

使用synchronized(obj)加锁时,obj对象所对应的Monitor对象就会发生改变:

刚开始 Monitor 中 Owner 为 null,当有线程Thread1执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread1,Monitor中只能有一个 Owner,就代表Thread1线程获取了锁,同时也会通过CAS操作修改obj对象头中的Mark Word,其状态为10,前三十位存放Monitor对象的指针

在 Thread1上锁的过程中,如果有其他线程Thread2,Thread3,Thread4 也来执行 synchronized(obj),此时他们就会进入EntryList阻塞队列中变成阻塞状态,直到Thread1线程释放锁后,这些阻塞的线程再去竞争获取锁

WaitSet中放的是之前获得过锁,但条件不满足进入 WAITING 状态的线程

这种加锁方式就是重量级锁

但是这种加锁方式在线程竞争很小的时候,同一个线程每次获取锁都要经过十分复杂的CAS操作,于是为了减少性能开销,java6之后便有了不同的默认加锁类型:偏向锁和轻量级锁


自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,自旋优化通过在获取锁时进行忙等待(即让线程处于不断循环的状态),而不是直接让线程挂起并进行上下文切换。这样可以避免因锁竞争而带来的线程切换开销,提高系统的整体性能

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。

  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。

  • Java 7 之后不能控制是否开启自旋功能


轻量级锁

轻量级锁并没有使用专门的 Monitor 对象来控制线程对对象的访问,而是每次使用synchronized(obj) 获取锁时在线程的栈帧中生成一个锁记录Lock Record,其结构如图:

Lock Record
Lock Record地址 | 00
Object reference

假设有一段代码:

static final Object obj = new Object();
public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}
}
public static void method2() {synchronized( obj ) {// 同步块 B}
}

1️⃣当method1第一次执行synchronized( obj )时,就会在当前线程的栈帧中生成一个锁记录Lock Record,并且让锁记录中 Object reference 指向锁对象obj,并尝试用 CAS 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录,如果 CAS 替换成功,对象头中存储了锁记录地址和状态 00,而锁记录中的信息则替换为Mark Word 的值,表示由该线程给对象加锁,而第一部分中提到,00表示轻量级锁

2️⃣method1中接着又调用了method2,又一次运行了synchronized( obj ),但是此时第一次加的锁还未释放,当查看obj的对象头时,发现对象头中存储的锁记录地址就是当前线程的,也就代表着当前线程想要重入锁,此时在栈帧中就会再生成一个锁记录Lock Record,但不会再次进行CAS替换操作,因为对象头中已经存储了锁记录的地址,而是直接将锁记录的第一部分设为null

Thread-01
Lock Record
Lock Record
Mark Word 的值
Object reference
n u l l
Object reference

3️⃣method2执行完毕释放锁时,由于锁记录中值为null,表示有重入,这时重置锁记录,表示重入计数减1,当method1退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头


轻量级锁适用于交替执行的场景轻量级锁 并不使用 Monitor 对象,它是通过在 Mark Word 中存储锁的状态信息来管理锁的。当线程竞争较少时,轻量级锁可以避免操作系统的 Monitor 锁,从而减少线程阻塞和上下文切换带来的性能开销


偏向锁

偏向锁是JVM为减少同步带来的性能开销而引入的一种优化机制。它假设在大多数情况下,某个对象的锁只会被同一个线程获取

当一个线程第一次获得锁时,JVM会在Mark Word中存储当前线程的ID,并将锁的状态设置为偏向锁。这样,后续如果同一个线程再次访问该锁时,它将不再进行同步操作,直接进入临界区。只有当其他线程尝试获取锁时,偏向锁才会被撤销,转为轻量级锁或其他类型的锁,对于单线程访问的情况,偏向锁几乎没有性能损失,因此能够显著提高程序的性能

这也是java6之后默认开启的锁类型

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0

  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟

  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

也就是说使用synchronized做线程同步时默认优先使用的就是偏向锁,如果此时发生了多线程竞争同一个锁对象时,就会发生锁膨胀


锁膨胀

指的是 轻量级锁(Lightweight Locking)在多线程竞争时升级为 重量级锁(Heavyweight Locking)的过程。这是 Java 中的一个优化机制,旨在尽量避免高竞争情况下带来的性能损失,同时保持线程同步的正确性

锁膨胀的触发条件

锁膨胀通常发生在 轻量级锁CAS 操作(Compare and Swap)无法成功时。具体触发条件如下:

  • 锁的竞争激烈:当多个线程同时竞争同一个锁时,轻量级锁的 CAS 操作会失败,因为每个线程都试图修改对象的 Mark Word(对象头中的一部分)以获取锁。如果 CAS 操作失败,说明该对象的锁已经被其他线程占用,这时轻量级锁会升级为重量级锁。
  • CAS 失败:轻量级锁依赖于 CAS 操作。如果多个线程同时竞争同一把锁,CAS 操作会因为竞争失败而触发锁膨胀。由于 CAS 操作是原子性的,因此只有一个线程能够成功更新对象的 Mark Word

过程示意图:

首次获取
出现竞争
自旋失败
批量撤销
释放锁
对象不可达
无锁
偏向锁
轻量级锁
重量级锁

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词