扒一扒synchronized的原理

在前面并发容器的学习中我们多次发现代码之中使用了Atomic包下的类,以及synchorized关键字,今天我们就来扒一扒 synchronized 关键字。 synchronized 关键字使用的三种场景

1.对于普通同步方法,锁是当前实例对象

2.对于静态同步方法,锁是当前类的Class对象

3.对于同步方法块,锁是Synchronized括号里配置的对象

接下来我们来看看synchronized关键字是如何实现的我们从jvm虚拟机规范中找到其根据

从这段描述中我们知道java的实现方案有两种 一种针对方法是使用ACC_SYNCHORNIZED标志后由方法调用和返回指令实现,而对于代码块的实现使用的是monitorenter 和 monitorexit指令来实现。

而从上面的描述中我们也知道为什么以前称synchronized是重量级锁,因为其由jvm底层实现,每次加锁是所谓的由用户态到内核态的转换。而我们的synchronized是java的宝贝儿子,从1.6开始了一波又一波的优化。现在已经不能单纯的说其效率低,性能消耗大了。我们首先确认synchronized存在哪里。从官方文档中我们可以找到是存在对象头之中的Mrak Word

具体结构如下

从1.6版本为了降低锁的性能消耗,引入了“偏向锁” 和“轻量级”锁 。锁一共有四种状态,分别是,无状态锁,偏向锁,轻量级锁和重量级锁。下面来说说锁升级

偏向锁出现的原因是jvm的开发者经过研究发现,大多数情况下,锁不存在竞争,并且总是由同一线程反复获得。所以为了降低获得锁的代价引入了偏向锁,偏向锁一般不会自动释放。除非达成某种条件才会释放锁。

获得偏向锁后ThreadId将指向获取锁的线程ID,如果尝试获取偏向锁失败需要先判断偏向锁标示位是否为1,然后尝试使用cas操作将偏向锁指向当前线程ID。当出现锁竞争的时候会将偏向锁升级为轻量级锁      

 

        

轻量级锁时,首先抢锁的线程会将当前 markword copy到自己的 线程栈的lock record中。接下来尝试使用cas操作将markword 指向当前线程 lock record的地址。如果成功则抢锁成功,如果失败则自旋抢锁。自旋操作虽然不阻塞当前线程,但是会损耗cpu资源。当自旋到达一定数量后,锁再次升级为重量级锁。(注意:锁只能升级不能降级

当进入重量级锁时,我们就提到了monitor对象 ,让我们翻看一下openjdk中 monitor实现在objectMonitor.hpp中代码如下

 


上图为线程运行时示意,下面我将简述该过程。首先线程进入EntrySet,抢锁,首先线程会去判断count是否为0 ,尝试使用cas操作改变count值为1,如果失败则进入waitSet,如果成果则将owner 指向当前线程。 如果count不为0时,则需要判断owner是否为当前线程如果时则count+1重入。如果不是当前线程则失败。线程释放锁时,首先判断当前线程是否为owner,如果是则执行count -1 ,并判断 count是否为0 如果不是则释放锁失败。直至count为0时才释放成功。

当线程执行wait/notify操作时。如果线程wait,则标示释放锁,该线程进入WaitSet中等待。由于EntrySet 和 WaitSet 同时竞争锁,所以synchronized为非公平锁。

对于synchronized关键字还有锁粗化,锁消除等优化

例如代码总是在带线程下使用,jvm则会对synchronized关键字进行消除。(锁消除)

例如 一个方法中中有多段synchronized关键字修饰的代码,jvm在不影响运行结果的前提下,会将部分代码移入synchronized修饰的代码块中,或将多个代码块合并。

今天的总结就到这里

欢迎大家指正问题 !!  与君共勉

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值