synchronized原理

对象头

假如对象是非数组类型,则没有 Array Length这一项。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

从上往下分别是 无锁,偏向锁,轻量锁,重量锁,GC标记。


Monitor(synchronized的重量锁通过它来实现)

机制

在这里插入图片描述

在这里插入图片描述

  1. _owner 指向持有ObjectMonitor对象的线程地址。
  2. _WaitSet 存放调用wait方法,而进入等待状态的线程的队列。
  3. _EntryList 这里是等待锁block状态的线程的队列。
  4. _recursions 锁的重入次数。
  5. _count 线程获取锁的次数。
    在这里插入图片描述

wait()、notify()

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


偏向锁

存在意义

偏向锁的目标是减少昂贵的原子指令CAS(Compare And Swap)等的使用(相比轻量锁减少了CAS,当然更减少了os的介入)。Monitor里面也有可重入机制,就不会重新将该对象对应的Monitor里面的Owner改变了。(个人认为:这里应该不涉及到互斥信号量的分配问题,就算是 不是同一个线程竞争到了锁也不会重新分配mutex呀,这个对象对应的monitor早都分配好了呀不需重新分配。)

应用场景(就是通过什么场景发现synchronized需要优化,进而诞生偏向锁)

  1. 类加载过程有很多方法都是加sychronized锁的,虽然可以并发地进行类加载,但大多数情况都是由main线程完成。(我不知道!网上看的,不知道是不是main线程!错了别打我,就是举个例子)
  2. 一些旧版本的库(Vector、HashTable等?)

偏向锁的设计

如若默认情况下偏向锁开启被延迟的话,-XX:BiasedLockingStartupDelay=0 可关于延迟。
如果确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁。
(毕竟。。。。。。。。。。。。。。。。。。。。。。。)

主体思想

%%%%%%%%%%%%%%
假如开启了偏向锁,当一个线程访问同步块并获取锁时,会在对象头存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开启了偏向锁的无锁->偏向锁->轻量锁

biased_lock=1。
在这里插入图片描述

刚开始无锁(这个无锁指的是没被任何线程进入过,不是不具备偏向锁,偏向锁不是一个真正的锁,就是代码层面去控制它而已),假如一个线程需要访问该同步块。则:

在这里插入图片描述
这个时候对象头的MarkWord(threId=null)没有存储着当前线程的偏向锁,通过CAS将对象头偏向锁的线程ID指向当前线程来竞争锁,变为:
这次CAS也可能竞争失败,也许被其他线程抢先了。(T__T)
在这里插入图片描述

  1. 一个线程在执行完同步代码块以后, 并不会尝试将 MarkWord 中的 thread ID 赋回原来的值 。这样做的好处是: 如果该线程需要再次对这个对象加锁,而这个对象之前一直没有被其他线程尝试获取过锁,依旧停留在可偏向的状态下, 即可在不修改对象头的情况下(即不使用CAS), 直接认为偏向成功。
  2. (只要threadID有值,且epoch不过期,CAS就一定会失败) 如果 CAS 操作失败, 则说明, 有另外一个线程 Thread B 抢先获取了偏向锁。 这种状态说明该对象的竞争比较激烈, 此时需要撤销 Thread B 获得的偏向锁,将 Thread B 持有的锁升级为轻量级锁(也可能重偏向)。 该操作需要等待全局安全点 JVM safepoint ( 此时间点, 没有线程在执行字节码) 。
重偏向

%%%%%%%%%%%%%%%%%

线程复用线程id

在这里插入图片描述
注释代码开启空线程后,线程id不同,不会被复用。而打印结果如下:(只是一种现象,没去看底层源码)
在这里插入图片描述

打印初始化信息

-XX:+PrintFlagsInitial
在这里插入图片描述

批量重偏向

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

批量重偏向撤销

在这里插入图片描述


轻量锁

轻量级锁加锁方式:
CAS(设置对象头轻量级锁标志)-> 执行代码块 -> CAS(重置对象头轻量级锁标志)
CAS相对于monitorenter和monitorexit的代价更小

存在意义

轻量级锁不是为了替换重量级锁的而是为了减少使用重量级锁,当轻量级锁失败后,会升级为重量级锁。轻量锁的目标是减少os的介入,减少系统调用(导致内核用户来回切换)。重量锁会让得不到锁的线程阻塞,回涉及到内核用户态的切换,很耗费时间。

使用场景

轻量锁会先让线程自旋一下等待锁,往往使用于多个线程串行访问同步块,或者竞争没那么大的时候。

轻量锁的设计

一个线程能同时访问一个加锁的方法吗?好像不行吧(偏向锁的出现是因为:同一个线程多次串行访问一个同步块,每次采用重锁又要每次都要尝试CAS将Monitor里面的ower(指的是拥有Monitor锁的线程)从null改为自己,是否成功,而且就算成功了,也需要通过底层os的实现,其实这里我是不太懂的。),所以根本不会出现轻量锁重入的情况,同一个线程只是串行访问一个同步块,轻量锁每次访问完就解锁,怎么会重入呢?一些博客简直在胡扯。

加锁

在这里插入图片描述
在这里插入图片描述
否则自旋获取锁一段事件后还是失败,则膨胀为重量级锁。
在这里插入图片描述

解锁

使用CAS将对象头替换为当前线程的栈帧中存储锁记录空间的MarkWord。若果成功,则表示没有竞争发生,解锁成功。若失败,表示当前锁存在竞争,锁可能已经升级为重量锁,则采用重量级解锁(见Monitor)。


锁的优缺点及整体设计思想

在这里插入图片描述
在这里插入图片描述

🔗

大佬太强了%%%%%:Java精通并发-通过openjdk源码分析ObjectMonitor底层实现
好想去找男朋友啊啊啊
不要男朋友了
我看不懂看不懂看不懂!:关于偏向锁,安全点,JIT的一些暗坑.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值