并发编程 synchronized (五) 重量级锁、轻量级锁

目录

Monitor(重量级锁)

对象头 

组成

工作机制

轻量级锁

加锁过程

锁重入

锁膨胀(转为重量级锁)

自旋


Monitor(重量级锁)

对象头 

 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级锁)之后,在该对象头的 Mark Word 中设置指向该 Monitor 的指针

Mark Word(64 位)

锁不同时,Mark Word 会发生变化

Klass Word 指向该对象的类型

组成

WaitSet:线程得到锁,但不满足执行条件,于是进入 WaitSet 等到满足条件并被唤醒,进入 EntryList 等待 

EntryList:当锁已被其他线程获取进入 EntryList 等待锁被释放并被唤醒,开始不公平竞争锁(不是先来后到) 

Owner:指向当前锁的主人

工作机制

Thread1 执行到 synchronized(object),此时 Monitor 的 owner 为空,接下来 owner 指向 Thread1

② 接着,Thread0、Thread2 也执行到 synchronized(object),此时 owner 不为空,则它们进入 EntryList 等待

Thread1 临界区代码执行完毕,释放锁并唤醒 EntryList 中线程,进行不公平竞争;竞争成功的,owner 指向该线程;竞争失败的,再次进入 EntryList

④ Thread3、Thread4 之后都先后得到了锁,但是它们执行条件未满足(没有视频会员无法跳过广告)进入 WaitSet 等待

轻量级锁

① 对于同一把锁,多个线程间没有竞争;即它们用锁的时间是错开的某段时间内只有一个线程使用这把锁;这时可以使用轻量级锁来优化

② 轻量级锁无需其他操作,同样是 synchronized一开始加锁都是使用轻量级锁,假如加锁失败,说明存在竞争;此时转为重量级锁(Monitor)

加锁过程

① 线程创建锁记录对象(Lock Record),每个线程的栈帧都包含锁记录这个结构,锁记录内部可以保存对象的 Mark Word

② 锁记录中 Reference Object 指向对象

③ 锁记录的地址和状态 00 与对象的 Mark Word 尝试用 CAS 操作(原子性操作)进行交换;若交换成功,代表线程对对象加锁成功(线程获取锁成功)

CAS 操作失败,代表已有线程获取该对象的轻量级锁;此时表示有竞争,进入锁膨胀

或者发生了锁重入

或者

锁重入

上述 CAS 操作失败原因之一

public void method1(){
    synchronized(object){
        method2();
    }
}

public void method2(){
    synchronized(object){
        // 执行代码
    }
}

当前线程多次获取同一把锁,这种操作就是锁重入;同样会产生锁记录,但是只用来计数

② 当解锁(释放锁)时,如果有取值为 null 的锁记录,表示有锁重入;这是重置锁记录,表示锁重入数减 1 

③ 当遇到取值不为 null 的锁记录时,再次尝试使用 CAS 操作将原本 Mark Word 中内容替换回去;假如成功,表示解锁(释放锁)成功

CAS 操作失败时,表示锁进行了锁膨胀或者已经升级为重量级锁(Monitor)

锁膨胀(转为重量级锁)

上述 CAS 操作失败原因之二,轻量级锁解锁失败原因之一

① 由于竞争,锁升级为重量级锁(Monitor);此时 Monitorowner Thread0

按之前的说法Thread1 应该进入 EntryList 等待;等到 Thread0 释放完锁唤醒 EntryList 中线程Thread1 才有机会获得锁???

自旋

 Thread1 不会马上进入 EntryList 等待 

它可以进行自旋重试;假如这时,Thread0 释放了锁Thread1 可以获得到锁避免阻塞

                自旋重试成功:

Thread0(core0 上)Mark WordThread1(core1 上)
尝试获取锁(monitor)10 重量级锁
获取锁成功10 重量级锁(重量级锁指针)
执行代码10 重量级锁(重量级锁指针)
执行代码10 重量级锁(重量级锁指针)尝试获取锁(monitor)
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行完毕10 重量级锁(重量级锁指针)自旋重试
释放锁成功001 无锁自旋重试
10 重量级锁(重量级锁指针)获取锁成功
10 重量级锁(重量级锁指针)执行代码
10 重量级锁(重量级锁指针)执行代码
......
......

                自旋重试失败(Thread0 执行时间过长,一直未释放锁):

Thread0(core0 上)Mark WordThread1(core1 上)
尝试获取锁(monitor)10 重量级锁
获取锁成功10 重量级锁(重量级锁指针)
执行代码10 重量级锁(重量级锁指针)
执行代码10 重量级锁(重量级锁指针)尝试获取锁(monitor)
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)自旋重试
执行代码10 重量级锁(重量级锁指针)阻塞
......
......

自旋注意事项 

1、自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 才能发挥优势

     ① 因为,单核 CPU 只能并发执行,当 Thread1 获取时间片进行自旋时;Thread0 无法执行代码更别说释放锁了;所以单核 CPU 自旋纯粹浪费

     ② 多核 CPU 可以让 Thread0、Thread1 并行执行;Thread0 执行代码同时,Thread1 在自旋

2、在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会 高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。

3、Java 7 之后不能控制是否开启自旋功能

4、无竞争时重量级锁会自动降为轻量级锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值