其他分享
首页 > 其他分享> > CAS和synchronized锁升级深入详解

CAS和synchronized锁升级深入详解

作者:互联网

 

 

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