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