Synchronized轻量级锁原理

加锁过程

1.在线程栈中创建一个Lock Record,将其obj(即Object reference)字段指向锁对象。

2.会把锁的Mark Word复制到自己的Lock Record的Displaced Mark Word里面。然后线程尝试直接通过CAS指令将Lock Record的地址存储在对象头的mark word中,如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。如果失败,进入到步骤3。

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分(Displaced Mark Word)为null,起到了一个重入计数器的作用。然后结束。

4.如果都失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,需要膨胀为重量级锁。【这就是轻量级锁升级为重量级锁的时机】

解锁过程

1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。

2.如果Lock Record的Displaced Mark Word为null,代表这是一次重入,将obj设置为null后continue。

3.如果Lock Record的Displaced Mark Word不为null,则利用CAS指令将对象头的mark word恢复成为Displaced Mark Word。如果成功,则continue,否则膨胀为重量级锁。

轻量级锁重入示例图

我们看个demo,在该demo中重复3次获得锁。

synchronized(obj){
    synchronized(obj){
    	synchronized(obj){
    	}
    }
}

轻量级锁什么时候升级为重量级锁?

其实在加锁的时候已经说过了,这里再以一个具体场景说下

  • 线程1获取轻量级锁时会把锁的Mark Word复制到自己的Lock Record的Displaced Mark Word里面。然后线程尝试直接通过CAS指令将Lock Record的地址存储在对象头的mark word中
  • 如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2在CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败。那么此时就代表发生了锁竞争,准备升级为重量级锁

轻量级锁CAS的问题

1、结论: 没有自旋这回事,只有重量级锁获取失败才会自旋,网上的文章好多都是错的,我个人认为轻量级锁的意义就是在没有线程争用锁时不用创建monitor。 【源码得到的结论,实践才是硬道理】

2、轻量级锁和偏向锁区别: 只要存在竞争就会升级重量级。轻量级锁的存在就是用于线程之间交替获取锁的场景,但是和偏向锁是有区别的啊。一个线程获取偏向锁之后,那么这个锁自然而然就属于这个线程(就算该线程释放了偏向锁也不会改变这把锁偏向这个线程的【也就是之前说的不会修改Thread ID】,这个前提是没有发生过批量重偏向使锁的epoch与其对应class类的epoch不相等)。所以说偏向锁的场景是用于一个线程不断的获取锁,如果把它放在轻量级锁的场景下线程之间交替获取的话会发生偏向锁的撤销的。也就是说在偏向锁的情况下,线程1之前释放了锁,线程2再获取锁,即使此时没有同时锁竞争的情况,依然是要升级为轻量级锁的。而轻量级锁只要没有同时去获取锁,就可以不升级为重量级锁,也就代表你可以不同线程交替获取这个锁。

3、效率上来看偏向锁只有在获取的时候进行一次CAS,以后的释放和获取只需要简单的一些判断操作。而轻量级锁的获取和释放都要都要CAS,单纯的看效率还是偏向锁效率高。

### Java 中 `synchronized` 关键字相关的机制 #### 偏向 (Biased Locking) 偏向旨在减少无竞争情况下的同步开销。当一个线程访问同步块并获取时,JVM会假设该只会被这个线程再次获取。因此,在第一次获得之后,后续对该的操作几乎没有任何成本。 - **工作原理**: JVM会在对象头的标记字段中存储首次定此对象的线程ID[^1]。 - **特点**: - 加和解无需额外消耗资源。 - 如果发生争用,则需要撤销偏向模式,转换成其他形式的。 - 对于单一线程频繁调用的情况特别有效。 ```java public class BiasedLockExample { private final Object monitor = new Object(); public void performAction() { synchronized(monitor) { // 初始状态下monitor处于未定状态;一旦某个线程获得了它,就会成为偏向 System.out.println(Thread.currentThread().getName()); } } } ``` #### 轻量级 (Lightweight Locking) 轻量级用于处理短时间内的轻微并发冲突。在这种情况下,多个线程可以轮流快速地占有同一把而不会造成严重的性能损失。 - **工作原理**: 当检测到有第二个线程试图进入临界区时,当前持有的会被提升至轻量级级别,并且尝试通过循环比较交换(CAS)的方式让后来者取得所有权而不必挂起自己[^4]。 - **特性**: - 非阻塞性质使得等待中的线程能够继续运行而不是立即停止。 - 若长时间无法得到则可能退化为重量级。 - 更适合那些预计会有短暂延迟但又确实会发生少量竞态条件的应用场合。 ```java // 这里展示的是伪代码逻辑, 实际上由JVM内部实现 if (!isLocked()) { acquireInExclusiveMode(); } else if (canBeUpgradedToLightWeight()) { upgradeAndTryAcquireViaCAS(); } else { escalateToHeavyWeightLock(); } ``` #### 重量级 (Heavyweight Locking) 这是最传统的互斥机制,通常意味着较高的上下文切换代价以及较低的整体效率。然而对于某些特定的任务来说却是必要的选择。 - **运作方式**: 所有的请求都将排队等候直至前序事务完成释放为止。在此期间任何新的参与者都得被迫休眠直到轮到了它们才行[^5]。 - **属性**: - 可能导致更高的CPU利用率因为涉及到操作系统层面的过程调度。 - 不过也正因为如此才得以保证绝对的安全性和顺序一致性。 - 主要应用于长期占用共享资源的情形下。 ```java class HeavyWeightLockDemo { static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args)throws InterruptedException{ Thread t1=new Thread(()->{try{lock.lock();System.out.println("Thread 1 holds the lock");Thread.sleep(20);}finally{lock.unlock();}}); Thread t2=new Thread(()->{try{lock.lock();System.out.println("Thread 2 holds the lock");}finally{lock.unlock();}}); t1.start();t2.start(); t1.join();t2.join(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值