synchronized锁膨胀过程验证

本文介绍了Java中synchronized锁的优化机制,包括偏向锁、轻量级锁和重量级锁,详细阐述了锁的膨胀升级过程,并通过代码验证了锁的状态变化。在JDK1.6以后,synchronized进行了优化,增加了锁膨胀升级等功能,以提高并发性能。文中还探讨了锁不能降级的原因,并通过示例展示了锁状态的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

synchronized的小故事

在并发编程中,对于synchronizedLock的使用是很频繁的。Lock是基于Java的一个框架,synchronized是基于C语言的JVM层级的锁,使用起来很方便。

但是呢,在jdk1.6以前,synchronized是一个重量级锁,他的底层需要操作系统来支持,必然涉及到了用户态和内核态的切换,还需要挂起当前线程,直到竞争到锁才执行,因此是很耗性能的。再者,一个C语言程序性能竟然不如Java的Lock框架性能,也是让人哭笑不得。

所以啊,Oracle公司决定对synchronized进行优化,在jdk1.6对该锁进行升级,增加了锁膨胀升级、锁粗化、锁消除等特性,使得两种锁的性能达到了差不多的水平。

本文重点介绍锁的膨胀升级,并使用代码验证其过程。


锁其实是一个对象

锁其实就是一个Object对象,对于新手来说,可能不好理解。这里简单解释一下,方便理解后面的概念。

synchronized是基于JVM内置锁——Monitor(监视器)实现。任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态,即可以执行同步块里的代码。synchronized在JVM里的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。

简单理解就是任何一个对象都可以成为一把锁,类的Class也可以成为一把锁,这把锁锁定的级别就是类级别,可以对静态方法进行锁定。

既然,锁是对象,那么回顾一下对象的布局:对象头、实例数据、对齐填充。锁相关的信息就保存在对象头里边。
在这里插入图片描述
HotSpot虚拟机的对象头包括两部分信息:Mark Word、类型信息,如果是数组需要多加一个数组长度。

Mark Word,用于存储对象自身的运行时数据, 如哈希码
(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键。

这部分数据的长度在32位和64位的虚拟机(暂不考虑开启压缩指针的场景)中分别为32个和64个Bits。考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

这里的Mark Word空间复用怎么理解呢?就是一块空间,会根据锁当前的状态(锁会升级,下文会介绍)进行调整,具体布局如下
在这里插入图片描述

假设当前是无锁状态,那么Mark Word保存的就是对象的hashcode、对象分代年龄、是否偏向、锁标志位
如果锁是轻量级锁,那么此时Mark Word保存的就是pinter to Lock Record,是一个指针,指针所指向的位置,会拷贝以根当前的Mark Word信息(分代年龄、hashcode…)


锁怎么膨胀升级

对于可能会有线程安全问题的代码,我们一般会对其加锁,使得程序能串行执行。但是很多时候,我们系统的并发度是很低的,甚至几乎没有出现过并发情况,这样加锁就有点浪费性能了(每次都让操作系统去加锁、释放锁)。

故此,可以多加几种锁,让锁能够适配不同场景,同时也能够根据合适的场景自动升级:偏向锁、轻量级锁、重量锁。这些锁是可以进行单向升级的,也就是说不能降级。

偏向锁

顾名思义,锁会偏向某一个线程。如果在一定时间内,只有一条线程去获取锁,那么就没有必要去中断线程、竞争锁。
如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,
无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

默认是开启偏向锁
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking

轻量级锁

对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的。倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

有些文章、博客会提到自旋锁,其实就是轻量级锁的一种实现:不中断线程,通过不断循环获取锁,当然这个循环是有次数限制的。

重量级锁

该锁就是需要由操作系统进行加锁、释放锁,性能开销较大,但是在激烈的锁竞争环境下,重量级锁性能会比多条线程不断自旋要来的好。


锁膨胀升级验证

首先引入一个能够动态打印对象头信息的依赖

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.16</version>
        </dependency>
public class MarkWordTest {
   

    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
   

        Thread t1 = new Thread(() -> {
   
            synchronized (lock) {
   
                try {
   
                    TimeUnit.MILLISECONDS.sleep(2)
### Java 中 `synchronized` 关键字的实现原理 `synchronized` 是一种用于控制多个线程对共享资源访问的关键字,在 Java 编程语言中被广泛应用于解决并发编程中的数据竞争问题。当一个方法或一段代码块被声明为 `synchronized` 后,意味着同一时刻只有一个线程可以执行该部分代码。 具体来说,`synchronized` 的工作原理依赖于对象内部的一个监视器(Monitor)。每当有线程要进入被标记为 `synchronized` 的区域时,都需要先获取这个;而一旦离开此区域,则会释放掉持有的[^1]。 对于同步方法而言,如果是一个实例方法,那么就是当前对象本身 (`this`);如果是静态方法的话,则是对应类的 Class 对象。通过这种方式来确保任何时间点上最多只有一个线程能持有特定对象上的并执行相应的操作。 ### 升级机制 为了提高性能表现,JVM 设计了一套复杂的优化策略——即所谓的“升级”。这一过程大致分为以下几个阶段: #### 轻量级 (Lightweight Locking) 轻量级定是在没有争用的情况下采用的一种高效加协议。在这种模式下,每个尝试获得的操作都会直接检查目标对象头内的标志位,以此判断是否有其他活动线程正在占用这把。如果没有发现冲突,则可以直接设置自己的标识完成快速占有动作而不必陷入重量级的竞争状态。 #### 偏向 (Biased Locking) 偏向是针对大多数情况下只会在单一线程间传递的特点设计出来的进一步简化方案。首次成功请求到某对象之后,不仅会给其打上已定标签还会记录下最初拥有者的身份信息。此后只要还是同一个线程再次试图重新获取相同位置处的就可以免去重复验证流程从而加快速度。 #### 重量级 (Heavyweight Locking) 只有当以上两种较为温和的方式都无法满足需求时才会真正进入到传统意义上的完全互斥等待队列里排队等候解通知。此时涉及到操作系统层面的支持,通常伴随着较高的上下文切换开销成本。 随着应用负载的变化情况,上述三种形式之间会发生动态转换:从无 -> 偏向 -> 轻量级 -> 最终演变为重量级过程被称为“膨胀”。 ### Java 内存模型(JMM) 和可见性保障 Java 内存模型规定了 JVM 如何处理多线程环境下的内存读写行为,并提供了必要的抽象层次使得开发者不必关心具体的硬件架构差异。其中一个重要特性便是保证了线程之间的 **可见性** :即某个线程所做的更改应当及时反映给其它可能观察这些变化的地方。 在涉及 `synchronized` 的场景中,每次退出临界区之前都要刷新本地缓存至主存之中,同时也会阻止编译期及运行时期内可能出现的危害程序逻辑正确性的指令重排序现象发生。因此即使存在不同的 CPU 核心或是处理器架构也能维持一致的行为特征[^2]。 ```java public class Counter { private int count; public synchronized void increment() { // 加入同步修饰符 this.count++; } public synchronized int getCount() { // 获取最新值也需要同步保护 return this.count; } } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值