ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CAS和synchronized锁升级深入详解

2022-04-26 22:04:05  阅读:180  来源: 互联网

标签:操作系统 synchronized CAS 详解 线程 内存 自旋 偏向


 

 

CAS  compare and swap
什么是CAS?
假设内存里面放的是0  我们现在多线程访问这个0 每个线程都想给这个0 加1
如果我们想让数据一致 必须先加锁sys   JUC这个包出现之后出现了CAS操作
CAS 把内存中的0 拿到CPU中做计算 做完计算后0变成1  然后把1 写回去
写回去的过程中要进行比较 看看这个内存里是否依旧为0  如果为0 说明没有被其他线程
修改  那么执行写操作,
如果不为0 那么一定被其他线程修改过
于是该线程会重新读取内存数据的修改值 然后再拿到CPU中做计算 计算后+1 再次写回
内存 写之前再次比较与之前CPU拿到的数值是否相同 如果相同 那么就修改内存的值
如果不想同 继续上述操作
我们还遗漏了一点  在线程A 读取内存数据到CPU时 若其他线程过来修改了内存的数据
但是返回的值为原来的0 那么就会出现ABA的问题  
为了解决问题 需要给该数据加上版本号  数值型 布尔类型

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CAS 是compare and exchange 是两条指令
在这两条指令执行期间 很有可能被其他线程干扰  那么就需要将CAS 操作进行上锁
所以CAS 底层还是实现了上锁的本质

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

用户态和内核态
作为操作系统来说,它能做的一些操作时不能允许普通用户的
如果普通用户想操作 那就必须想操作系统申请
为了保证操作系统的健壮性  都会将操作命令分成级别
有些指令普通用户可以访问 有些指令只能通过操作系统才能访问
将程序的执行过程  分成了用户态和内核态

    在早期 jdk1.0-1.2 synchronized 是重量级锁  因为申请锁资源必须经过kernel 系统调用jvm 是出于用户态  操作系统是内核态  当jvm 要添加锁是synchronized 需要向内核申请 的系统调用  0x80(执行过程?)
返回也是需要经过内核 都需要从用户态到内核态的转换 和内核态到用户态的转换 现在版本将synchroized 进行了优化 在上锁的某些状态之下 不需要向操作系统申请在用户态就能解决问题
对象的内存布局
markWorld

当我们new出一个对象 他是在内存中怎么分布的?
hospot new出对象后 比如new T 这个class类 里面有个成员变量int m 当他在堆内存中是怎么样的布局?
1 8字节markword
2 默认情况下 4字节的classPointer 这个指针可以找到t.class 默认时开启压缩的
3 instanceData int 类型 占4字节
4 padding(8字节对齐) 这个对象的大小务必是8的整数倍 不够padding 补到能够被8整除

证明工具

 

 

 

 

 

当加上synchronized 它的锁信息添加在markword里面
锁升级过程
  当我们首先new出一个普通对象 一旦我给这个对象加上关键字synchronized的时候 他会升级为偏向锁
一旦这个锁竞争激烈 他就会升级为轻量级锁(自旋锁或者) 如果竞争再加剧 那么就会升级为重量级锁 需要向操作系统内核申请锁zne

怎么区分锁的状态?
在markword里面优先看最低的两位 锁标志位 00轻量级锁 10重量级锁 11说明这个对象正在被回收
01 包含两种状态 一种是 无锁 一种是偏向锁 、
为了区分01 这两种状态 那么在最低两位前面一位 为偏向锁位 0 为无锁(001) 1为偏向锁(101)
偏向锁和轻量级锁 都是用户空间锁(不需要和操作系统打交道) 偏向锁和自旋锁都是用户空间完成
重量级锁需要向内核申请
什么叫偏向锁 (前提**)
 当只有一个线程访问的时候 没有必要设计锁竞争机制 第一个访问这把锁的线程直接将线程ID 写到markword 里面 就可以了
什么叫自旋锁
   竞争加剧的时候 会把这把锁的线程ID撤销(偏向锁撤销) 两个线程竞争(多个)【自旋锁竞争】
自旋锁竞争的过程
  每个线程都有自己的线程栈 每个线程会在自己的线程栈中生成LR(LockRecord 锁记录) 他们用自旋的范式 把自己的LR放到keyword里面
竞争成功后会有指针指向一个线程 那么那个线程就持有这把锁 另外的线程CAS 继续竞争(不停的询问是否释放锁的过程)

什么叫重量级锁
   这把锁必须想操作系统申请 LockRecord记录的是ObjectMoniter 这个是jvm空间写的C++对象
这个C++对象 需要通过操作系统 拿到操作系统对应的那把锁 申请到锁 你才能持有 才能锁定

 

 

 

 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

 

可以发现 下面还有一个monitorexit  这是出现异常的时候才会退出

 

 

if(UseBiasedLocking)如果使用了偏向锁    fast_enter
如果成功了 就成功偏向锁 如果不成功会进入到slow_enter 锁升级过程
否则就是slow_enter
slow_enter 首先进入自旋 升级为自旋锁 如果自旋锁不成功 就锁膨胀就进入重量级锁
synchronized 是可重入锁
两个方法锁的同一个对象 
可重入锁必须记录 因为要解锁几次必须对应

偏向锁,自旋锁 记录在线程栈  没重入一次 LR+1  
重量级锁记录在ObjectMointor
自旋锁什么时候升级为重量级锁
    
为什么有自旋锁 还需要重量级锁?
    自旋是占用CPU资源的 如果锁的时间长  或者自旋线程多  CPU会被大量消耗
重量级锁 里面有各种队列 将自旋锁扔进 wait_set队列里,
在队列里不消耗CPU资源
什么叫偏向锁一起动 什么叫偏向锁未启动

偏向锁是否一定比自旋锁效率高?
只有一个线程的时候偏向锁效率最高,但是多线程情况下 效率并不比自旋锁效率高,
因为偏向锁需要涉及到锁撤销 这时候直接使用自旋锁
jvm 启动过程 会有很多线程竞争 所以默认情况 启动时不打开偏向锁,
过一段时间 再打开
-XX:BiasedLockingStartupDelay=0 //开始启动偏向锁

 

 

 

 

 

 

偏向锁一开始没启动 是001
偏向锁开始启动  是101  由于刚开始还没有偏向任何一个线程 也叫匿名偏向
什么时候有偏向锁进入 重量级锁
调用wait 方法 

 

标签:操作系统,synchronized,CAS,详解,线程,内存,自旋,偏向
来源: https://www.cnblogs.com/Lcch/p/16196817.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有