偏向锁,轻量级锁,重量级锁,自旋锁,锁升级

写了这篇之后的某一天,看到了大佬的文章:https://blog.csdn.net/javazejian/article/details/72828483,
对偏向,轻量,重量锁有了新对认识,就在开头将它补上:
只有一条线程获取,偏向锁
多条线程获取,但是是分时间交替的,轻量锁
多条线程竞争,同一时间有多条线程竞争,重量锁

==================================================

参考自:http://www.infoq.com/cn/articles/java-se-16-synchronized
目前等锁有两种机制,基于JVM等synchronized和基于JDK的LOCK类。本文只关注前者。
首先了解一下synchronized的作用对象
对于某个方法,synchronized锁住的是当前实例对象(this)
对于静态方法,作用的是当前对象的CLASS对象
作用与某个对象实例时,锁住的就是对应的代码块.
看过JVM就会知道,每个对象都有锁,可以管它叫monitor,也就是监视器。每个monitor都有一对相对应的monitorenter和monitorexit。线程执行到monitorenter时候,就会尝试获取这个monitor的所有权,也就是尝试着获取该对象的锁。而锁的信息就保存在java对象头中,主要看里面的Mark Word.

在这这个就是JAVA对象头里zhe插入图片描述
这个就是JAVA对象头。

32位与64位的虚拟机中,Mark Word的结构有些不同.先看32位的结构:
在这里插入图片描述
再看64位:
在这里插入图片描述
它可能会变动,如果锁标志位变化了,结构也会有所变化。下面是几种“不同”锁的标志位:
在这里插入图片描述
先说无锁—>偏向锁。锁的标志位都为01。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

要注意的是,偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。下图中的线程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程。(感谢大佬的图)
在这里插入图片描述
而一但偏向锁发生竞争了,那么就会升级位轻量级锁。

轻量级锁->重量级锁:
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。
在这里插入图片描述
关于自旋锁,可以当成这个线程通过无限循环(其实是有次数的,好像JVM中默认是50次,50次完了还没获取到锁的话就会膨胀成重量级锁,重量级锁就会导致阻塞,释放锁的时候才会取唤醒开始新一轮的竞争)来持续的尝试获取锁。要注意的是,自旋锁是发生在轻量锁–>重量锁之间的,自旋50次后还不能获取才膨胀为重量锁做自旋的原因可以理解成:我认为这个锁马上就会释放了,当前占有这个锁的人跳不了几天了,这时候如果我回趟家再过来(用户/内核态切换),实在是浪费时间,不如在这里坐会儿。但也有坏处,因为一直无限循环,很浪费系统资源。(简单来说在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的, 但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行, 这种切换的代价是非常昂贵的;然而在现实中的大部分情况下,同步方法是运行在 单线程环境(无锁竞争环境)如果每次都调用Mutex Lock那么将严重的影响程序的性能。 )
再补上一个看到的说的清晰的线程获取资源流程
参考自:http://xly1981.iteye.com/blog/1766224:

每一个线程在准备获取共享资源时:
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord,
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。

最后,最关键的JVM层面的图来了,我早点看到这个也能少很多痛苦
转载:https://blog.csdn.net/javazejian/article/details/72828483
在这里插入图片描述
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值