简单了解JavaCAS的相关知识原理
作者:赐我白日梦 发布时间:2023-03-25 12:09:31
JMM与问题引入
为啥先说JMM,因为CAS的实现类中维护的变量都被volatile修饰, 这个volatile 是遵循JMM规范(不是百分百遵循,下文会说)实现的保证多线程并发访问某个变量实现线程安全的手段
一连串的知识点慢慢缕
首先说什么是JMM, JMM就是大家所说的java的内存模型, 它是人们在逻辑上做出的划分, 或者可以将JMM当成是一种规范, 有哪些规范呢? 如下
可见性: 某一个线程对内存中的变量做出改动后,要求其他的线程在第一事件内马上马得到通知,在CAS的实现中, 可见性其实是通过不断的while循环读取而得到的通知, 而不是被动的得到通知
原子性: 线程在执行某个操作的时,要么一起成功,要么就一起失败
有序性: 为了提高性能, 编译器处理器会进行指令的重排序, 源码-> 编译器优化重排 -> 处理器优化重排 -> 内存系统重排 -> 最终执行的命令
JVM运行的实体是线程, 每一个线程在创建之后JVM都会为其创建一个工作空间, 这个工作空间是每一个线程之间的私有空间, 并且任何两条线程之间的都不能直接访问到对方的工作空间, 线程之间的通信,必须通过共享空间来中转完成
JMM规定所有的变量全部存在主内存中,主内存是一块共享空间,那么如果某个线程相对主内存中共享变量做出修改怎么办呢? 像
下面这样:
将共享变量的副本拷贝到工作空间中
对变量进行赋值修改
将工作空间中的变量写回到内存中
JMM还规定如下:
任何线程在解锁前必须将工作空间的共享变量立即刷新进内存中
线程在加锁前必须读取主内存中的值更新到自己的工作空间中
加锁和解锁是同一把锁
问题引入
这时候如果多个线程并发按照上面的三步走去访问主内存中的共享变量的话就会出现线程安全性的问题, 比如说 现在主内存中的共享变量是c=1, 有AB两个线程去并发访问这个c变量, 都想进行c++, 现在A将c拷贝到自己的工作空间进行c++, 于是c=2 , 于此同时线程B也进行c++, c在B的工作空间中=2, AB线程将结果写回工作空间最终的结果就是2, 而不是我们预期的3
相信怎么解决大家都知道, 就是使用JUC,中的原子类就能规避这个问题
而原子类的底层实现使用的就是CAS技术
什么是CAS
CAS(compare and swap) 顾名思义: 比较和交换,在JUC中原子类的底层使用的都是CAS无锁实现线程安全,是一门很炫的技术
如下面两行代码, 先比较再交换, 即: 如果从主内存中读取到的值为4就将它更新为2019
AtomicInteger atomicInteger = new AtomicInteger(4);
atomicInteger.compareAndSet(4,2019);
跟进AtomicInteger的源码如下, 底层维护着一个int 类型的 变量, (当然是因为我选择的原来类是AtomicInteger类型), 并且这个int类型的值被 volatile 修饰
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
什么是volatile
volatile是JVM提供的轻量的同步机制, 为什么是轻量界别呢? , 刚才在上面说了JMM规范中提到了三条特性, 而JVM提供的volatile仅仅满足上面的规范中的 2/3, 如下:
保证可见性
不保证原子性
禁止指令重排序
单独的volatile是不能满足原子性的,即如下代码在多线程并发访问的情况下依然会出现线程安全性问题
private volatile int value;
public void add(){
value++;
}
那么JUC的原子类是如何实现的 可以满足原子性呢? 于是就不得不说本片博文的主角, CAS
CAS源码跟进
我们跟进AtomicInteger中的先递增再获取的方法 incrementAndGet()
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
通过代码我们看到调用了Unsafe类来实现
什么是Unsafe类?
进入Unsafe类,可以看到他里面存在大量的 native方法,这些native方法全部是空方法,
这个unsafe类其实相当于一个后门,他是java去访问调用系统上 C C++ 函数类库的方法 如下图
继续跟进这个方法incrementAndGet() 于是我们就来到了我们的主角方法, 关于这个方法倒是不难理解,主要是搞清楚方法中的var12345到底代表什么就行, 如下代码+注释
var1: 上一个方法传递进来的: this,即当前对象
var2: 上一个方法传递进来的valueOffset, 就是内存地址偏移量
通过这个内存地址偏移量我能精确的找到要操作的变量在内存中的地址
var4: 上一个方法传递进来的1, 就是每次增长的值
var5: 通过this和内存地址偏移量读取出来的当前内存中的目标值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
注意它用的是while循环, 相对if(flag){} 这种写法会多一次判断, 整体的思路就是 在进行修改之前先进行一次比较,如果读取到的当前值和预期值是相同的,就自增,否则的话就继续轮询修改
小总结
通过上面的过程, 其实就能总结出CAS的底层实现原理
volatile
自旋锁
unsafe类
补充: CAS通过Native方法的底层实现,本质上是操作系统层面上的CPU的并发原语,JVM会直接实现出汇编层面的指令,依赖于硬件去实现, 此外, 对于CPU的原语来说, 有两条特性1,必定连续, 2.不被中断
CAS的优缺点
优点:
它的底层我们看到了通过do-while 实现的自旋锁来实现, 就省去了在多个线程之间进行切换所带来的额外的上下文切换的开销
缺点:
通过while循环不断的尝试获取, 省去了上下文切换的开销,但是占用cpu的资源
CAS只能保证一个共享变量的原子性, 如果存在多个共享变量的话不得不加锁实现
存在ABA问题
ABA问题
什么是ABA问题
我们这样玩, 还是AB两个线程, 给AtomicInteger赋初始值0
A线程中的代码如下:
Thread.sleep(3000);
atomicInteger.compareAndSet(0,2019);
B线程中的代码如下:
atomicInteger.compareAndSet(0,1);
atomicInteger.compareAndSet(1,0);
AB线程同时启动, 虽然最终的结果A线程能成果的将值修改成2019,,但是它不能感知到在他睡眠过程中B线程对数据进行过改变, 换句话说就是A线程被B线程欺骗了
ABA问题的解决--- AtomicStampedRefernce.java
带时间戳的原子引用, 实现的机制就是通过 原子引用+版本号来完成, 每次对指定值的修改相应的版本号会加1, 实例如下
// 0表示初始化, 1表示初始版本号
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(0, 1);
reference.getStamp(); // 获取版本号
reference.attemptStamp(1,2); // 期待是1, 如果是1就更新为2
原子引用
JUC中我们可以找到像AtomicInteger这样已经定义好了实现类, 但是JUC没有给我们提供类似这样 AtomicUser或者 AtomicProduct 这样自定义类型的原子引用类型啊, 不过java仍然是提供了后门就是 原子引用类型
使用实例:
User user = getUserById(1);
AtomicReference<User> userAtomicReference = new AtomicReference<User>();
user.setUsername("张三");
userAtomicReference.compareAndSet(user,user);
来源:https://www.cnblogs.com/ZhuChangwu/p/11939124.html


猜你喜欢
- 1、思维导图2、什么是MVC?MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(con
- 本文实例为大家分享了java实现超市管理系统的具体代码,供大家参考,具体内容如下实现功能使用选择结构,循环结构,数组的知识实现一个超市管理系
- 方案实施1、 spring和ehcache集成主要获取ehcache作为操作ehcache的对象。spring.xml中注入ehcacheM
- 嗯,就是BASE64,不用多想,本来计划是要跟上一篇字符串压缩一起写的,用来实现将一个文件可以用json或者text等方式进行接口之间的传输
- 一:讲故事上一篇介绍的 6 个特性从园子里的反馈来看效果不错,那这一篇就再带来 6 个特性同大家一起欣赏。二:特性分析1. 像弱类型语言一样
- 接着上篇java验证码制作(上篇)给大家介绍有关java验证码的相关知识!方法三:用开源组件Jcaptcha实现,与Spring组合使用可产
- SpringBoot2.x过后static下的静态资源无法访问package com.example.thymeleaf.commons;i
- boot-admin整合Quartz实现动态管理定时任务淄博烧烤爆红出了圈,当你坐在八大局的烧烤摊,面前是火炉、烤串、小饼和蘸料,音乐响起,
- 本文实例讲述了C#非矩形窗体实现方法。分享给大家供大家参考。具体实现方法如下:using System;using System.Colle
- 非Web程序 1.AppDomain.CurrentDomain.BaseDirectory 2.Environment.CurrentDi
- 前言Android开发中经常使用findViewById来获取控件然后进行一些列操作,当控件太多的时候代码就非常臃肿,今天就来学习一个新的开
- 1、创建Windows服务 说明:a)Description 服务描述,直接显示到Windows服务列表中的描述;b)Displa
- 一.瀑布模型瀑布模型严格遵循软件生命周期各阶段的固定顺序:计划、分析、设计、编程、训试和维护,上一阶段完成后才能进入到下一阶段, 整个模型就
- 不知不觉这个春节也已经过完了,遗憾家里没网,没能及时给大家送上祝福,今天回到深圳,明天就要上班了,小伙伴们是不是和我一样呢?今天讲的是一个大
- 环境配置:Jdk1.8 + Tomcat8.5 + mysql + Eclispe(IntelliJ IDEA,Eclispe,MyEcli
- SpringBoot中的过滤器 * 操作与springmvc中的几乎一样所以这里也不过多介绍了,下面举两
- 本文实例讲述了java基于swing实现的五子棋游戏代码。分享给大家供大家参考。主要功能代码如下:import java.awt.*;imp
- 在上面的例子中多次使用到了Thread类的join方法。我想大家可能已经猜出来join方法的功能是什么了。对,join方法的功能就是使异步执
- 1.简述描述:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。1.此题对比原题有改动2.题目保
- 什么是耦合性耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、