synchronized 简单执行过程

本文详细解释了synchronized关键字在并发编程中的作用,包括如何在字节码层面实现线程同步,以及MONITORENTER和MONITOREXIT指令的作用。作者还讨论了数据一致性通过总线嗅探机制和synchronized对有序性的局限性,以及后续解决方案的提及。

并发编程三大原则之初识synchronized-优快云博客

在上一篇文章中,我们已经知道了,synchronized关键字的基本使用了。那么我们是不是需要了解一下,这个关键字到底在程序中是如何使用的,是怎么保证并发编程的,三个原则的。

首先,我们看一下在我们的java程序被编译成 class 文件后,这个关键字在 class 文件中,是已什么样的方式展示出来的。

这个是.java文件中的定义。

这个是 字节码中,synchronized 所展示出来的样子,就是把 synchronized 所包含的代码块,使用了两个 指令 给包起来了。

MONITORENTER  进入监视器。

MONITOREXIT 退出监视器。

以上是从 java 文件到字节码的展示结果。

我对于监视器的两个关键字的理解就是,在程序中,添加了提示装置,当程序读到 MONITORENTER 这个指令的时候,那么 jvm 就会知道我即将运行的是一段被 synchronized 修饰的代码段。

MONITOR 在jvm所对应的就是ObjectMonitor 对象,这个对象里面包含了很多信息。

拿一个比较重要的信息举例:

owner:一开始这个是 null 一旦有线程操作,同步代码块,那么这个owner就会被赋值成为当前对象。在owner没有被赋值为 null 的时候,其他线程是无法操作同步代码块的内容。(这个在我看来的话就是保证了线程执行代码的原子性,就是你关上门,自己吃苹果,你没有吃完,其他人进不去

于是乎,jvm 就开始对该对象的 markworld 进行分析,得出当前锁的一个状态,得出来不同的状态值后呢,还会得出当前对象是个什么级别的锁。然后 jvm 在调用C++代码中,不同的 case 块,来执行不同的逻辑,举个例子:

在 C++ 层面执行代码的时候,会把 class 翻译成汇编码,丢给操作系统去完成,需要做的事情。

这里面还有一个事情就是在 执行 synchronized 代码块的时候,汇编指令前面会加上一个 lock 指令,在 cpu 看到有 lock 指令的时候,就会触发 总线嗅探机制,这个机制的目的是为了保证数据的一致性。当多线程在操作共享变量的时候,一个线程操作完,会立马通过总线刷新到主存中,在经过总线的时候,总线就会通知其他使用到,该变量的线程内部的副本变量,就会失效,用的时候必须要从主存中重新加载才行。

这里面的总线,你简单的理解成,CPU 和 主存 之间连线。

通过 加上 lock 指令,就可以保证并发编程的,数据的可见性。

那么有序性是怎么保证的呢,这个在 synchronized 并没有很好的体现出来,因为这个关键字保证不了有序性,后面会通过 DCL 的实现方式来验证该问题。也会给出保证有序性的解决方案。

Java 中的 `synchronized` 锁在 JVM 中通过锁升级机制来优化多线程环境下的性能表现。这种机制根据线程竞争锁的情况,动态地调整锁的状态,以在保证线程安全的同时,尽可能降低锁的开销。以下是锁升级的完整过程和机制: ### 无锁状态(No Lock) 在对象未被任何线程锁定的情况下,其处于无锁状态。此时对象头中的 Mark Word 保存的是对象的哈希码、对象年龄等信息。当一个线程尝试获取锁时,如果发现锁处于无锁状态,它可以通过 CAS(Compare and Swap)操作尝试将自身线程 ID 写入对象头中,从而获取偏向锁[^3]。 ### 偏向锁(Biased Locking) 偏向锁的核心思想是“无竞争时无需同步”,适用于单线程反复进入同步块的场景。一旦某个线程成功获取偏向锁,JVM 会将对象头的 Mark Word 设置为偏向锁模式,并记录持有锁的线程 ID。后续该线程再次进入同步块时,无需任何同步操作即可直接执行。只有当其他线程尝试获取锁时,偏向锁才会被撤销并升级为轻量级锁[^2]。 #### 偏向锁的撤销 偏向锁的撤销需要等待全局安全点(Safe Point),并暂停持有锁的线程,检查其是否仍在使用该锁。若未使用,则将对象恢复为无锁状态;若仍在使用,则升级为轻量级锁。 ### 轻量级锁(Lightweight Locking) 当多个线程交替获取锁但不存在长时间竞争时,JVM 使用轻量级锁来减少线程阻塞带来的开销。线程在栈帧中创建锁记录(Lock Record),通过 CAS 操作尝试将对象头的 Mark Word 替换为指向锁记录的指针。如果成功,则获得轻量级锁;如果失败,说明存在竞争,JVM 会尝试自旋(Spin)一段时间[^3]。 #### 自旋锁(Spin Lock) 在轻量级锁状态下,如果线程未能立即获取锁,JVM 会允许其进行一定次数的自旋(默认次数由 JVM 自动调整),以减少线程阻塞和上下文切换的开销。如果自旋成功,则继续执行;如果自旋失败,锁将升级为重量级锁。 ### 重量级锁(Heavyweight Locking) 当自旋次数超过阈值或线程竞争激烈时,锁会升级为重量级锁。此时,JVM 会将线程挂起并进入操作系统内核态的阻塞状态,通过操作系统提供的互斥量(Mutex)来管理线程的同步。虽然重量级锁的性能开销较大,但它适用于高并发场景,能有效防止线程“饿死”问题[^1]。 ### 锁升级流程总结 - **无锁 → 偏向锁**:单线程访问,JVM 启用偏向锁优化。 - **偏向锁 → 轻量级锁**:出现线程竞争,偏向锁撤销后升级为轻量级锁。 - **轻量级锁 → 重量级锁**:自旋失败或竞争加剧,锁升级为重量级锁。 - **重量级锁 → 无锁**:锁释放后,对象恢复为无锁状态。 ### 示例代码:锁升级过程演示 以下是一个简单Java 示例,用于演示锁的使用和升级过程: ```java public class LockUpgradeExample { private Object lock = new Object(); public void method() { synchronized (lock) { // 同步代码块 System.out.println("Locked by thread: " + Thread.currentThread().getName()); } } public static void main(String[] args) { LockUpgradeExample example = new LockUpgradeExample(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { example.method(); } }, "Thread-1"); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { example.method(); } }, "Thread-2"); t1.start(); t2.start(); } } ``` 在上述代码中,随着线程 `t1` 和 `t2` 的交替执行,锁的状态会从无锁升级为偏向锁,随后在竞争加剧时升级为轻量级锁,最终可能升级为重量级锁。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值