synchronized轻量级锁原理

946 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情

加锁过程

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 RecordDisplaced Mark Word为null,代表这是一次重入,将obj设置为null后continue。

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

轻量级锁重入示例图

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

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

image.png

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

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

  • 线程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,单纯的看效率还是偏向锁效率高。