【juc】monitor锁

22 篇文章 0 订阅

一、概念
  • 1.monitor即监视器或者管程
  • 2.每一个java对象都可以关联一个monitor对象
  • 3.WaitSet:等待的线程集
  • 4.EntryList:等待队列、阻塞队列
  • 5.Owner:记录当前持有锁的线程
二、重量级锁
  • 1.synchronized重量级锁给对象上锁之后,对象的对象头中的mark word中就被设置指向monitor对象的指针。没加锁的时候,32位的mark word中25个bit位存hashcode,4bit存age分代年龄,1bit存是否偏向,2bit存锁标志位,锁标志位为01。加synchronized锁时,32位的mark word存指向monitor对象的指针,2bit存锁标志位,锁标志位为10
  • 2.当线程1给对象A上了synchronized锁,对象A关联的monitor的owner则为线程1。当线程2准备synchronized(对象A)的时候,检查对象A关联的monitor的状态,此时monitor的owner是线程1,owner只能有一个线程,线程2则进入monitor的EntryList阻塞队列中,此时线程2进入blocked阻塞状态
  • 3.当线程3准备执行synchronized(对象A)的时候,检查对象A关联的monitor的状态,此时monitor的owner是线程1,线程3则进入monitor的EntryList阻塞队列中,此时线程3进入blocked阻塞状态
  • 4.当线程1执行完synchronized中的临界区代码,owner会与线程1断开空出来后会通知monitor的EntryList中的线程,EntryList中的线程唤醒后继续竞争,若线程3竞争成功,线程3则成为monitor新的owner,线程2继续阻塞
  • 5.竞争锁是非公平的,先进入EntryList中的线程未必先获取到锁,是jdk底层实现决定的
  • 6.synchronized必须是进入同一个对象的monitor才有作用,不加synchronized的对象不会关联monitor
三、字节码说明
  • 1.monitorenter: 将对象的MarkWord置为Monitor的指针
  • 2.monitorexit:将对象MarkWord重置,唤醒EntryList
  • 3.如果synchronized(对象A){代码抛出异常},会有一个Exception Table记录代码的起始点,也会执行monitorexit指令,将对象MarkWord重置,唤醒EntryList,然后抛出异常
四、轻量级锁
  • 1.使用场景:一个对象虽然有多个线程访问,但多个线程访问的时间是错开的(没有竞争),可以使用轻量级锁优化
  • 2.同步方法块,利用一个对象A加锁,线程的栈帧会包含一个锁记录的结构,创建一个jvm层面的锁记录(Lock Record)对象
  • 3.锁记录中包含 对象指针(引用地址)和 锁记录地址和轻量级锁标识00,加锁后,对象指针会指向对象A,并且拿锁记录的地址和轻量级锁标识00去交换对象A的对象头的mark word,如果交换成功了,对象A的对象头中的mark word存的是锁记录的地址和轻量级锁标识00,锁记录中存的则是对象A的对象头markword之前存的信息(hashcode、分代gc年龄、是否偏向),该线程则对对象A加上了锁
  • 4.交换是原子操作,不会被打断,用cas方式实现的
  • 5.如果其它线程已经持有了对象A的轻量级锁,说明是有锁竞争的,此刻再加锁是会失败的,会进入锁膨胀
  • 6.如果synchronized(对象A)代码块中再次synchronized(对象A),也就是同一个线程对一个对象加两次锁,此时锁是重入的,则需要再加一条锁记录(Lock Record)作为重入的计数(后面再重入锁,锁记录再+1)
  • 7.重入的锁记录中的对象指针仍会指向对象A,但此时cas交换是会失败的,因为对象A中的对象头的mark word存的是线程第一次创建的锁记录的地址和轻量级锁标志00,但重入的锁记录还是能判断出是同一个线程加的锁,重入的锁记录地址为null
  • 8.当退出synchronized代码块(解锁时),如果有锁记录地址为null的,表示有重入,然后去掉该锁记录,重入计数也减一
  • 9.当退出synchronized代码块(解锁时),如果锁记录地址不为null,则是线程加的第一个锁记录,然后使用cas将锁记录存的mark word的值还原给对象A的对象头,还原成功则解锁成功,还原失败则说明轻量级锁进行了锁膨胀或者已经升级为重量级锁,则需要重量级锁的解锁流程来解锁
五、锁膨胀
  • 1.在尝试加轻量级锁的过程中,如果交换锁记录地址和对象头mark word中的信息失败,也就是cas操作失败,且识别的不是同一个线程,说明是有其它线程想给对象加上轻量级锁,此时就有了锁竞争,则需要进行锁膨胀,将轻量级变成重量级锁
  • 2.如果对象A的对象头中的mark word存的是线程1的锁记录中的地址和轻量级锁标识00,线程2此刻再加锁则失败,会为对象A申请一个monitor锁(重量级锁),让对象A的对象头的mark word存储重量级锁的地址,线程2则进入monitor锁中的EntryList中进行阻塞,monitor锁中的owner为线程1
  • 3.当线程1退出同步块解锁时,使用cas将mark word的值恢复个对象A的对象头,因为对象A对象头的mark word存的是重量级锁的地址,所以解锁会失败,会进入重量级锁的解锁流程,通过monitor地址找到monitor对象,将monitor的owner改为null,唤醒monitor的EntryList中的阻塞线程2
六、自旋优化
  • 1.重量级锁竞争的话,可以用自旋来进行优化,线程2进入阻塞之前会自旋,如果自旋成功获取到了锁,之前持有锁的线程1已经退出了同步块,释放了锁,那么线程2可以避免进入阻塞,从而拿到锁,线程2进入阻塞的话会发生cpu上下文切换,会耗费性能
  • 2.自旋发生在多核cpu的情况下,单核cpu在执行线程1同步代码块的内容,自旋的话线程2没有额外的cpu执行,自旋会占用cpu时间,单核cpu自旋会浪费cpu资源,多核才能发挥优势
  • 3.当线程1一直在执行同步代码块的内容,线程2自旋几次都没获取到锁,也是会进入阻塞
  • 4.自旋的次数:java6之后自旋是自适应的,如果对象A刚刚上次自旋操作成功过,则认为这次自旋的可能性很大,就会多自旋几次,如果上次自旋操作失败了,就会少自旋几次或者不自旋
  • 5.java7之后不能控制是否开启自旋功能
七、偏向锁
  • 1.轻量级锁在没有竞争时,每次重入仍然需要执行cas操作来替换对象头中的mark word,这种cas操作也是会消耗一定性能的
  • 2.java6中引入偏向锁做进一步优化,只有第一次使用cas将线程id设置到对象的mark word头里,之后发现这个线程id是自己的就表示没有竞争,不用重新cas来比较。只要不发生竞争,那么对象就归该线程所有
  • 3.开了偏向锁(默认是开启的),对象创建后,对象头的mark word的值最后三位是101,此时它的thread、epoch、age都为0
  • 4.偏向锁默认是延迟的,程序启动时不会立即生效,可以通过加vm参数 xx:BiasedLockingStartupDelay=0来禁用延迟
  • 5.如果没有开启偏向锁,对象在创建后,mark word最后3位为001,hashcode、age都为0,第一次用到hashcode时才会赋值
  • 6.添加VM参数-XX:-UseBiasedLocking可以用来禁用偏向锁
  • 7.如果一个对象是偏向状态,然后调用了对象.hashCode()方法,此时会禁用对象的偏向锁,因为调了hashCode方法,对象头中的mark word需要存储hashcode,偏向状态的对象头的mark word里线程id占据54bit,正常状态的对象头的mark word中的hashcode占用31bit,不够存储,所以会把偏向状态的对象改为正常状态下的对象
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王佑辉

老板,赏点吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值