Lightweight Synchronized

本文详细解析了Java6中引入的轻量锁原理及其实现过程,包括轻量锁与重量锁的区别,轻量锁的获取与释放机制,以及锁重入和锁的释放流程。

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

在java6之前,内置锁synchronized都是重量锁。其实现原理和Mutex类似,可以这样理解:建立一个Monitor,每个Monitor有一个等待队列和计数器,这个计数器的初始值是0。当某个线程获取到锁的时候,也就是计数器值非负,计数器的值减1。否则,进入到等待队列,进入休眠状态。当计数器的值恢复到0的时候,从等待队列中唤醒某个线程。

1)从上面的原理可以看出,在只有一个线程获取锁的时候,都需要建立Monitor对象以及等待队列等。其资源消耗相对较大。

2)并且有数据表明,对于绝大多数的锁,在整个同步周期内部是不存在竞争的。

根据上面两条依据,在java6中引入了轻量锁的概念。在轻量锁中用到了之前文章中提到的mark word。以下的几步描述了,线程获取锁到释放锁的步骤。

a)线程尝试获取锁之前


JVM在线程中建立一个叫做Lock Record的空间,这个空间用于存储加锁对象的Mark Word的副本。在官方的文档中称为Displaced Mark Word。

b)线程获取到锁


JVM将Mark Word的副本复制到Lock Record中。然后线程使用CAS操作尝试获取锁。这里的CAS操作可以使用如下的伪代码表示:

前提

1)将CAS操作记为 CAS(expect, really, update),希望的值是expect,真实的值是really,如果expect == really,则更新值为update。

2)将Lock Record记为LR,将加锁对象的Mark Word记为MW,将Lock Record对应的地址记为PLR。


那么,CAS(LR, MW, PLR)就可以表示当前的CAS操作。


当线程A尝试获取锁的时候,会比较当前Lock Record的值和Mark Word的值是否相同,如果相同则表示还没有其他线程获取锁,线程A可以获取锁,此时将Mark Word更新为指向Lock Record的指针,同时修改标志位为00。如果CAS失败,并不代表着获取锁失败。因为存在着以下的两种情况,

1)其他线程已经获取到锁

2)线程A已经获取了锁,这时Mark Word早已经被更新成了指向Lock Record的地址,所以CAS会失败,但是这种情况应该是可以获取成功的。

因此当CAS失败的时候,JVM首先会检查加锁对象的Mark Word是否指向了当前线程的栈帧,并且标志位是00,如果是则表明当前线程已经获取了这个对象的锁。否则表明其他线程获取了锁,,则这个线程通过自旋获取锁,在自旋失败一定次数后,将当前需要获取的对象锁升级为重量锁。

自旋锁:

阻塞锁是指当线程获取锁失败的时候,就进入休眠,等待唤醒。线程的休眠与唤醒代价较高。而自旋锁则表示当获取锁失败的时候,通过一定数量的空循环来占用CPU,而不是将线程阻塞至休眠。这样就不需要将线程唤醒,虽然消耗了一部分的CPU资源,但是相对于阻塞锁性能提升还是明显的。

升级为重量级锁之后,会将标志位修改为10,同时Mark Word存储指向重量级锁(互斥量)的指针,后面等待所的线程也会进入阻塞状态。


c)锁重入



d)锁的释放


### Java `synchronized` 关键字工作原理 #### 锁机制概述 `synchronized` 是 Java 中用于控制多个线程对共享资源访问的关键字。它通过提供一种内置锁的方式,确保同一时刻只有一个线程能够执行被保护的代码段[^1]。 #### 使用场景与方式 `synchronized` 主要应用于以下三种情况: - **实例方法**:当声明为 `synchronized` 的实例方法被调用时,该对象上的所有其他 `synchronized` 方法都将等待当前方法完成。 - **静态方法**:如果标记的是静态方法,则锁定整个类级别的锁,即 Class 对象,在这种情况下,所有的此类实例都会受到影响。 - **代码块**:可以指定特定的对象作为锁参数,从而更灵活地定义加锁范围,适用于细粒度控制的情况。 ```java public class SyncExample { private final Object lock = new Object(); public void methodA() { synchronized (lock) { // 显式指定锁对象 // 只有一个线程能进入此区域 } } public synchronized void instanceMethod() { // 实例方法默认使用 this 作为锁 } public static synchronized void staticMethod() { // 静态方法使用 .class 文件对应的 Class 对象作为锁 } } ``` #### 底层实现细节 在 JVM 层面,每当遇到 `monitorenter` 和 `monitorexit` 字节码指令时就会触发锁的操作。对于未竞争的状态下,JVM 尽量减少不必要的重量级同步开销;而对于存在多线程争夺同一个锁的情形,则会逐步升级锁状态以提高性能效率[^2]。 完整的锁升级路径如下: 1. 偏向锁(Biased Locking) 2. 轻量级锁(Lightweight Locking) 3. 重量级锁(Heavyweight Locking) 每一步骤都是为了适应不同的并发程度而设计的不同层次的优化措施[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值