synchronized

synchronized的锁有三种,重量级锁,轻量级锁,偏向锁。下面先从重量级锁开始讲。

1.重量级锁

要理解重量级锁,先了解两个概念,对象头Monitor

1.1对象头

先看下对象头图解

普通对象

数组对象

 

其中 Mark Word 结构为

 

64 位虚拟机 Mark Word

 

2.Monitor

Monitor被翻译成监视器或管程,其实就是真正的

Monitor是由操作系统用C语言创建的一种数据结构,它由这三部分Owner,EntryList,WaitSet组成。

Owner:当前正在执行的线程,锁的持有者。

EntryList:这里面是未获取到锁的线程,处于阻塞状态。

WaitSet:线程获取到了锁,但是未满足执行条件,调用wait()方法进入WaitSet等待。

synchronized(obj){

        //临界代码区

}

当使用重量级锁时

第一个线程Thread1到来时,它会将obj对象和Monitor对象关联,通过用obj的MarkWord前62为存储Monitor对象的地址,后两位设置成10,声明他为重量级锁。

当第一个线程Thread1还没执行完成,第二个线程Thread2到来时,它通过存储在obj的MarkWord内Monitor对象地址找到Monitor对象,发现它的Owner不为空,这时它进入阻塞队列EntryList,当第一个线程仍未执行完,后续到来的线程同上。

当一个线程执行完了,它会将Monitor对象的Owner置为null,唤醒在EntryList中阻塞的队列。

2.轻量级锁

使用Monitor对象是非常耗费性能的,所以如果一个对象虽然有多线程访问,但是多个线程访问的时间是错开的,也就是不存在竞争,那么可以使用轻量级锁优化。

 

看上图中obj对象的图解

HashCode 哈希码

Age 分代年龄

Bias 是否为偏向锁(占1bit,1为偏向锁)

01 锁类型 

001无锁

101 偏向锁

00 轻量级锁

10重量级锁

使用轻量级锁时,当第一个线程Thread0执行到同步代码块时,它首先会在自己的虚拟机栈的栈帧中创建一个锁记录对象(LockRecord),让Object Refence指向锁对象,并尝试用CAS替换obj对象的MarkWord,将MarkWord存入锁记录

如果CAS替换成功,对象头存储了锁记录的地址和状态,表示由该线程给对象加锁

如果CAS失败,有两种情况

如果其他线程已经持有了该Object轻量级锁,这时表明有竞争,进入锁膨胀的过程

如果自己执行了synchronized的锁重入,那么再添加一个LockRecord作为重入的计数

 

解锁时,如果有取值为null值得锁记录,表示有重入,这时重置锁记录,表示重入计数减一

当不为null时,这是会CAS将MarkWord得值恢复给对象头

成功,解锁成功

失败,说明,轻量级锁升级为重量级锁,进入重量级锁得解锁流程

 

 当Thread0还未执行完,Thread1线程来了,这时他发现Object对象的MarkWord区域已经被其他线程占了,这时发生了竞争,发生锁膨胀,轻量级锁升级为重量级锁

即为Object对象申请Monitor锁,让Object指向重量级锁

然后让自己进入EntryList中阻塞

当Thread0退出同步代码块解锁时,使用CAS将MarkWord恢复到对象头,失败,这是会进入重量级锁的解锁流程,即按照Monitor地址找到Monitor,设置Owner为空,唤醒EntryList的线程。

3.自旋优化

重量级锁发生竞争时,还可以使用自旋进行优化,当Thread0占有了锁时,Thread1再来,这是他不会立即阻塞,它会自旋重试几次,如果成功了,他就不要进入阻塞状态,也有自旋失败的情况。

再Java6之后自旋是自适应的,自旋成功高,它就会多自旋几次,反之,就会少自旋几次,甚至不自旋,总之,比较智能。

自旋要多核CPU才能发挥优势

Java7之后不能控制是否开启自旋功能

4.偏向锁

轻量级锁在只有自己这个线程时,它每次操作还要进行CAS操作

Java6引入了偏向锁来做进一步优化,只有第一次使用CAS操作时将线程ID设置到对象头的MarkWord中,之后发现这个线程ID是自己的就表示没有竞争,不用CAS,以后只要不发生竞争,这个对象就归该线程所有。

偏向锁是默认开启的,也是默认延迟的不会在程序时立即生效,如果想避免延迟,可以加VM参数

将其他线程使用偏向锁时,将升级为轻量级锁

调用wait / notify 时,会升级为重量级锁,因为要使用到Monitor

批量重偏向

如果对象被多个线程访问,但没有发生竞争,这时偏向线程T1的对象,仍有机会偏向线程T2,重偏向会重置对象的ThreadID

当撤销偏向锁阈值达到20次后,jvm会这样觉得,我是不是偏向错了呢,于是会给这些对象加锁时重新偏向至加锁线程

批量撤销

当撤销偏向锁阈值达到40次后,jvm会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变得不可偏向,新建的对象也是不可偏向的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值