synchronized底层如何实现?什么是锁的升级、降级?

在上一篇专栏中主要介绍了Synchronized和ReentrantLock有着如何的区别,相信也对这两种锁已经有了初步的认识了,那么今天就对我们平时的惯用锁synchronized进行一个底层的了解。

简述

synchronized

首先,对synchronized的代码块先说明一下,它是由一对儿monitorenter/monitorexit指令实现的,Monitor对象是同步的基本实现单元。

在jdk6之前,Monitor的实现依赖于操作系统内部的互斥锁,因为需要用户态到内核态的切换,所以同步操作都是一个无差别的重量级的行为,这也是为什么sync一直比ReentrantLock的效率要低的原因。

现代的jdk中,jvm对此进行了大刀阔斧的改进,提供了三种不同的Monitor实现,也就是三种锁,偏斜锁,轻量级锁和重量级锁,改善了性能。

所谓锁定升降级,就是jvm优化sync运行的机制,当jvm检测到不同的竞争状态的时候,会自动切换到合适的锁,这就是锁的升降级。

在无竞争时,默认使用偏斜锁。jvm利用CAS操作,在对象头上的Mark Word部分设置线程id,标明当前对象偏向于当前线程,所以不影响互斥所,主要是因为大部分对象的生命周期最多被一个线程锁定,所以使用偏斜锁可以降低无竞争开销。

但如果有线程需要某个被偏斜锁绑定的对象,jvm就要撤销偏斜锁,并且切换到轻量级锁,轻量级锁依赖CAS操作Mark Word来试图获取到锁,如果能获取到就用轻量级锁,如果不行,就用重量级锁。

而当jvm进行安全点的时候,就会检查会不会有闲置的Monitor,然后就会试图请求降级了。

以上就是sync基本点构造。

目标

此篇文章主要是考察对java内置锁实现的掌握,也是java并发的经典题目,涵盖了一些基本概念,建议遇到不会的就去查,文章也会去标注一些晦涩难懂的词汇的诠释。

建议各位在上班之前有限的时间内多深入学习,不然上班就没有时间往底层发展了。真正的提高,总是依赖于实践。

后面会进一步分析,sync的底层实现,可以把它当做一个入手点去看。

然后则是Concurrent包下的锁实现,毕竟ReentrantLock只是众多锁的一种。

下面会对于sync分块进行一下介绍。

扩展

既然上面提到了三种锁,那么下面就来解释一下。

  • 当jvm启动的时候,我们手动指定是否开启偏斜锁

偏斜锁并不是适用于所有场景,因为撤销操作是很重量级的,所以具体用不用偏斜锁得看实际场景,只有当存在很多无竞争关系的sync的时候,才能有明显改善。

  • 当指定开启偏斜锁

当我们选择开启偏斜锁之后,我们就会立刻获取到完整锁。

在这里插入图片描述
获取完整锁的逻辑如下(图源自极客时间):

  1. biasedLocking定义了偏斜锁的操作,revoke_and_rebias是获取偏斜锁的入口方法,revoke_at_safepoint定义了当检测到安全点时候的处理逻辑。
  2. 如果偏斜锁失败,就会进入轻量级锁的获取逻辑,也就是slow_enter
  3. biasedLocking是通过CAS设置Mark Word就完全够用了,对象头中Mark Word的结构,可以参考如下

在这里插入图片描述

  • 当指定关闭偏斜锁

那么就会直接进入轻量级锁的获取逻辑
在这里插入图片描述
(图源自极客时间)
实现思路如下:

  1. 设置Displaced Header , 然后利用cas_set_mark设置对象Mark Word,如果成功,就成功获取到轻量级锁。
  2. 否则Displaced Header , 进入锁膨胀阶段,具体实现在inflate方法中。
    锁膨胀:最高效的是偏向锁,尽量使用偏向锁,如果不能(发生了竞争)就膨胀为轻量级锁,这样优化的效率不如原来高不过还是一种优化(对比重量级锁而言)。

接下来说一些java核心类库其他锁的类型:

在这里插入图片描述

我们可以发现,这些锁不都是实现Lock接口,ReadWriteLock是一个单独的接口,其分别对应着只读和写操作,标准库中提供了再入版本的读写锁实现,对应的语意和ReentrantLock类似。

那么这些读写锁的意义又在哪里呢?

我们之前介绍过了Sync和ReentrantLock再入锁,但是这两种锁都有一定的局限性,通俗的说就是,0和1,要不就不占,要不就独占,那么该如何进一步提高并发操作的粒度呢?

java并发包提供了读写锁等扩展的锁的能力,因为读是不互斥的,也不会更改数据,所以就会不会存在干扰的问题。而写操作,就会涉及到并发一致性的问题,所以写线程之间,读写线程之间,需要精心涉及互斥逻辑。

下面就是一个基于读写锁实现的数据机构。

在这里插入图片描述

在运行中,如果读锁视图锁定时,写锁是被某个线程持有的,读锁将无法获得,而只好等待对方操作结束,这样就可以自动保证不会读取到争议的数据。

读取锁比sync的粒度更细,但是会产生很大的开销,所以后期就引入了StampedLock,在提供读写锁的同时,还可以优化。优化读基于假设,大多数情况下读与写不冲突,逻辑上是先试着修改,然后用validate方法确定进入写模式,如果没有进入,就成功避免了开销。如果进入,就尝试获取读锁。

在这里插入图片描述
注意,这里的writeLock和unLockWrite一定要保证成对调用。
你可能很好奇这些显式锁的实现机制,Java并发包内的各种同步工具,不仅仅是各种Lock,其他的如Semaphore、CountDownLatch,甚至是早期的FutureTask等,都是基于一 种AQS框架

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值