Synchronized原理(轻量级锁篇)

Synchronized原理(轻量级锁篇)

简述

介绍

轻量级锁是JDK1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

引入轻量级锁的目的

JVM的开发者发现在很多情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。

优点

竞争的线程不会阻塞,使用自选,提高程序响应速度。

缺点

如果一直不能获取到锁,长时间的自旋会造成CPU消耗。

适用场景

适用于少量线程竞争锁对象,且线程持有锁的时间不长,追求响应速度的场景。


工作流程

在这里插入图片描述

入口

轻量级锁的进入方式有三种

  • 对象处于未锁定不可偏状态

    在这里插入图片描述

    此状态下对象不能进入偏向锁模式,当有线程尝试获取锁时,会通过轻量级锁的方式获取锁。

  • 对象锁已经偏向于线程(不考虑重偏向场景)

    在这里插入图片描述

    当锁已经偏向于线程,且线程处于锁定状态或处于未锁定但不允许重偏向的情况下,其它的线程尝试获取锁时,会触发偏向锁撤销,然后升级为轻量级或重量级锁定。

  • 对象被轻量级锁定

    在这里插入图片描述

    当对象已经被轻量级锁定的时候,会判断是否是锁重入,如果是重入的话,会记录一条Displaced Mark Word为空的Lock Record。如果不是重入,会膨胀为重量级锁。需要注意的是,即使膨胀为重量级锁,没有获取到锁的线程也不会马上阻塞,而是通过适应性自旋尝试获取锁,当自旋次数达到临界值后,才会阻塞未获取到的线程。JVM认为获取到锁的线程大概率会很快的释放锁,这样做是为了尽可能的避免用户态到内核态的切换。


加锁过程

code 1 :判断对象是否是无锁状态(低三位 = 001),如果是,执行code 2,如果不是,执行code 4

code 2:在栈中建立一个Lock Record,将无锁状态的Mark Word拷贝到锁记录的Displaced Mark Word中,将owner指向当前对象。

code 3:尝试通过CAS 将锁对象的 Mark Word 更新为指向Lock Record的指针,如果更新成功,该线程获取到轻量级锁,并且需要把对象头的Mark Word的低两位改成10(注意这里修改的是对象头的Mark WordLock Record中记录的还是无锁状态的Mark Word);如果更新失败,执行code 4

code 4:对象是轻量级锁定状态,判断对象头的 Mark Word是否指向当前线程的栈帧。如果是,则这次为锁重入,将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录重入,该线程重入轻量级锁。如果不是,执行code 5

code 5:线程获取轻量级锁失败,锁膨胀为重量级锁,对象头的Mark Word改为指向重量级锁monitor的指针。获取失败的线程不会立即阻塞,先适应性自旋,尝试获取锁。到达临界值后,阻塞该线程,直到被唤醒。

适应性自旋

自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

图解
无锁状态获取锁

线程执行到同步块时,同步对象处于无锁状态,锁标志位为01,偏向标志位为0,偏向锁被禁用,对象处于无锁态。

在这里插入图片描述

在加锁前,虚拟机需要在当前线程的栈帧中建立锁记录(Lock Record)的空间。Lock Record 中包含一个 _displaced_header 属性,用于存储锁对象的 Mark Word 的拷贝。

在这里插入图片描述

将锁对象的 Mark Word 复制到锁记录中,这个复制过来的记录叫做 Displaced Mark Word。具体来讲,是将 mark word 放到锁记录的 _displaced_header 属性中,将Owner指向当前对象。

在这里插入图片描述

虚拟机使用 CAS 操作尝试将锁对象的 Mark Word 更新为指向锁记录的指针。如果更新成功,这个线程就获得了该对象的锁。

在这里插入图片描述

更新成功后,需要修改原对象头Mark Word中的锁状态标志位为00,目的是告诉其它线程此对象已经被轻量级锁定。

在这里插入图片描述

重入锁

当对象处于加锁状态时,会去检验Mark Word是否指向当前线程的栈帧,如果是则将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录线程重入锁。

在这里插入图片描述

非重入锁

如果指向的不是当前线程的栈帧则会触发锁膨胀,膨胀为重量级锁。

在这里插入图片描述
Thread2把锁膨胀为重量级锁,Thread1获取到重量级锁,Thread2适应性自旋尝试获取锁,到达临界值后进入等待队列阻塞。
在这里插入图片描述
这里注意执行锁膨胀的线程为Thread2,而非原轻量级锁的持有者Thread1


解锁过程

轻量级锁加锁时有锁重入的可能,同样的,在解锁时也需要判断是否是锁重入解锁。

code 1 :检索当前线程栈中的锁记录空间,从低位往高位找到第一条和此对象有关的Lock Record。加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null,相应的,在解锁时需要判断Displaced Mark Word是否为 null,如果是,则说明是锁重入解锁,移除onwer的指向,不做替换操作;如果不是,执行code 2

code 2:通过CAS把当前线程栈帧Lock Record中的Displaced Mark Word替换到对象头的Mark Word中去,如果替换成功,则轻量级解锁成功;如果替换失败,则说明发生了锁膨胀,对象现在是重量级锁定状态,执行code 3

code 3:执行重量级锁释放流程(详细过程下篇文章会将),释放重量级锁,同时唤醒被阻塞的线程去获取锁。

图解
非重入释放锁

解锁的思路是使用 CAS 操作把当前线程的栈帧中的 Displaced Mark Word 替换回锁对象中去,如果替换成功,则解锁成功。

在这里插入图片描述

CAS成功

在这里插入图片描述

CAS失败

在这里插入图片描述

轻量级锁未释放前被其它线程尝试获取,此时Mark Word指针已经被替换为指向Monitor,释放锁时CAS会失败,此时需要走重量级解锁流程。

PS:关于重量级解锁流程下篇文章会补充。

重入释放锁

加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null。相对应地,解锁时,如果判断 Displaced Mark Word 为 null 则说明是锁重入,不做替换操作。

在这里插入图片描述


归纳总结

轻量级锁的适用场景

少量的线程竞争锁,且所有者线程占用锁的事件补偿,追求响应速度的场景。

什么时候会升级为轻量级锁

当对象的偏向模式被关闭、对象处于已偏向已锁定、已偏向未锁定但不支持重偏向的场景下,就会升级为轻量级锁。

什么时候会升级为重量级锁

当竞争产生时就会升级为重量级锁,比如,两个线程同时获取锁,成功的线程会获取到轻量级锁,失败的线程会执行锁膨胀,升级为重量级锁。

轻量级锁怎样实现锁重入

当轻量级锁已经被线程持有,且对象头的Mark Word指向的是当前线程的栈帧时,会把本条Lock RecordDisplaced Mark Word 设置为 null,实现锁重入。当重入解锁时,只需要修改所有者onwer的指向。

轻量级锁是否会自旋

轻量级锁流程不会自旋,自旋发生在产生竞争后,获取失败的线程将锁膨胀为重量级锁。失败的线程不会立刻阻塞,而是先尝试适应性自旋,等待所有者释放锁,当到达临界值后再阻塞。

End

轻量级锁可以看作是偏向锁重偏向的升级版,加入了有锁无锁的状态转换,即使当竞争产生时升级到重量级锁,也不会马上阻塞线程,而是通过适应性自旋来决定是否阻塞,提高了性能。轻量级锁相较于偏向锁来说,简单一些,下一篇会着重介绍重量级锁,以及自旋的线程是怎样进入重量级锁的等待队列的。

参考文献

jdk源码剖析二: 对象内存布局、synchronized终极原理 - 只会一点java - 博客园 (cnblogs.com)

死磕Synchronized底层实现–轻量级锁 · Issue #14 · farmerjohngit/myblog (github.com)

  • 33
    点赞
  • 133
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值