CAS定义

CAS(Compare And Swap)是一种无锁算法。CAS算法是乐观锁的一种实现。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当预期值A和内存值V相同时,将内存值V修改为B并返回true,否则返回false。

CAS与synchronized

(1)synchronized加锁,同一时间段只允许一个线程访问,能够保证一致性但是并发性下降。

(2)CAS是一个自旋锁算法,使用do-while不断判断(没有加锁),保证一致性和并发性,但是比较消耗CPU资源。使用CAS就可以不用加锁来实现线程安全。

  • 原子性保证:CAS算法依赖于rt.jar包下的sun.misc.Unsafe类,该类中的所有方法都是native修饰的,直接调用操作系统底层资源执行相应的任务。
  • 内存可见性和禁止指令重排序的保证:AtomicXxx类中的成员变量value是由volatile修饰的:private volatile int value;

CAS算法的缺点

CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

  • 循环时间长、开销很大。

当某一方法比如:getAndAddInt执行时,如果CAS失败,会一直进行尝试。如果CAS长时间尝试但是一直不成功,可能会给CPU带来很大的开销。

  • 只能保证一个共享变量的原子操作。

当操作1个共享变量时,我们可以使用循环CAS的方式来保证原子操作,但是操作多个共享变量时,循环CAS就无法保证操作的原子性,这个时候就需要用锁来保证原子性。

  • 存在ABA问题

如果一个线程在初次读取时的值为A,并且在赋值的时候检查该值仍然是A,但是可能在这两次操作,之间有另外一个线程现将变量的值改成了B,然后又将该值改回为A,那么CAS会误认为该变量没有变化过。

CAS底层原理

sum.misc.Unsafe类中有多个方法被native关键字标记,这说明该方法是原生态的方法,它是一个调用非java语言的接口,也就是说这个接口的实现是其他语言实现的。CAS并发原语就是体现在java的sum.misc.Unsafe类中的各个方法,调用这个类中的CAS方法JVM就会通过其他语言生成若干条系统指令,完整这些指令的过程中,是不允许被中断的,所以CAS是一条CUP的原子指令,所以它不会造成数据不一致问题。

多线程情况下,number变量每次++都会出现线程安全问题,AtomicInteger则不会,因为它保证了原子性。

我们进去看,getAndIncrement调用的就是Unsafe类中的getAndAddInt方法,this表示当前对象,valueOffset表示变量值在内存中的偏移量(也就是内存地址)

我们再进入Unsafe类看看var1就是getAndIncrement方法传过来的对象,var2是系统偏移量,这里是使用了do-while循环,一开始循环就通过var1对象和var2偏移量获取期望值var5,进入循环,compareAndSwapInt方法被native关键字标记的,所以他是原子性的 ,var2的值与var的值相等时,则使用新的值var5+var4,返回true,循环条件取反则结束循环,否则如果var2与var5不相等就继续循环,直到条件不满足再跳出循环

// unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取对象var1,偏移量为var2地址上的值,并赋值给var5
var5 = this.getIntVolatile(var1, var2);
/**
* 再次获取对象var1,偏移量var2地址上的值,并和var5进行比较:
* - 如果不相等,返回false,继续执行do-while循环
* - 如果相等,将返回的var5数值和var4相加并返回
*/
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
// 最终总是返回对象var1,偏移量为var2地址上的值,即上述所说的V。
return var5;
}

ABA问题解决方案

使用AtomicStampedReference或者AtomicMarkableReference来解决CAS的ABA问题,思路类似于SVN版本号,SpringBoot热部署中trigger.txt

AtomicStampedReference解决方案:每次修改都会让stamp值加1,类似于版本控制号

package com.raicho.mianshi.mycas;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference; /**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 10:19
**/
public class AtomicStampedReferenceABA {
private static AtomicReference<Integer> ar = new AtomicReference<>(0);
private static AtomicStampedReference<Integer> asr =
new AtomicStampedReference<>(0, 1); public static void main(String[] args) {
System.out.println("=============演示ABA问题(AtomicReference)===========");
new Thread(() -> {
ar.compareAndSet(0, 1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ar.compareAndSet(1, 0);
System.out.println(Thread.currentThread().getName() + "进行了一次ABA操作");
}, "子线程").start(); try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} boolean res = ar.compareAndSet(0, 100);
if (res) {
System.out.println("main成功修改, 未察觉到子线程进行了ABA操作");
} System.out.println("=============解决ABA问题(AtomicStampReference)===========");
new Thread(() -> {
int curStamp = asr.getStamp();
System.out.println("t1获取当前stamp: " + curStamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
asr.compareAndSet(0, 1, curStamp, curStamp + 1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
asr.compareAndSet(1, 0, asr.getStamp(), asr.getStamp() + 1);
}, "t1").start(); new Thread(() -> {
int curStamp = asr.getStamp();
System.out.println("t2获取当前stamp: " + curStamp);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = asr.compareAndSet(0, 100, curStamp, curStamp + 1);
if (!result) {
System.out.println("修改失败! 预期stamp: " + curStamp + ", 实际stamp: " + asr.getStamp());
}
}, "t2").start();
}
}

运行结果:

AtomicMarkableReference:如果不关心引用变量中途被修改了多少次,而只关心是否被修改过,可以使用AtomicMarkableReference:

package com.raicho.mianshi.mycas;

import java.util.concurrent.atomic.AtomicMarkableReference;

/**
* @author: Raicho
* @Description:
* @program: mianshi
* @create: 2020-07-17 10:46
**/
public class AtomicMarkableReferenceABA {
private static AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<>(0, false); public static void main(String[] args) {
new Thread(() -> {
amr.compareAndSet(0, 1, false, true);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
amr.compareAndSet(1, 0, true, true);
System.out.println("子线程进行了ABA修改!");
}, "子线程").start(); try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} boolean res = amr.compareAndSet(0, 100, false, true);
if (!res) {
System.out.println("修改失败! 当前isMarked: " + amr.isMarked());
}
}
}

运行结果:

参考

知乎:https://zhuanlan.zhihu.com/p/93418208

csdn:https://blog.csdn.net/justry_deng/article/details/83449038

CAS底层原理与ABA问题的更多相关文章

  1. java面试-CAS底层原理

    一.CAS是什么? 比较并交换,它是一条CPU并发原语. CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B.当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什 ...

  2. java - CAS及CAS底层原理

    CAS是什么? CAS的全称为Compare-And-Swap它是一条CPU并发原语,也就是在CPU硬件层面上来说比较并且判断是否设置新值这段操作是原子性的,不会被其他线程所打断.在JAVA并发包ja ...

  3. 并发编程之 CAS 的原理

    前言 在并发编程中,锁是消耗性能的操作,同一时间只能有一个线程进入同步块修改变量的值,比如下面的代码 synchronized void function(int b){ a = a + b: } 如 ...

  4. CAS(乐观锁)与ABA问题

    cas是什么 CAS 全称 compare and swap 或者compare and exchange  比较并且交换.用于在没有锁的情况下,多个线程对同一个值的更新. cas原理 例如,我们对一 ...

  5. 并发之volatile底层原理

    15.深入分析Volatile的实现原理 14.java多线程编程底层原理剖析以及volatile原理 13.Java中Volatile底层原理与应用 12.Java多线程-java.util.con ...

  6. 一文彻底搞懂CAS实现原理 & 深入到CPU指令

    本文导读: 前言 如何保障线程安全 CAS原理剖析 CPU如何保证原子操作 解密CAS底层指令 小结 朋友,文章优先发布公众号,如果你愿意,可否扫文末二维码关注下? 前言 日常编码过程中,基本不会直接 ...

  7. 最简单的HashMap底层原理介绍

    HashMap 底层原理  1.HashMap底层概述 2.JDK1.7实现方式 3.JDK1.8实现方式 4.关键名词 5.相关问题 1.HashMap底层概述 在JDK1.7中HashMap采用的 ...

  8. Java面试底层原理

    面试发现经常有些重复的面试问题,自己也应该学会记录下来,最好自己能做成笔记,在下一次面的时候说得有条不紊,深入具体,面试官想必也很开心.以下是我个人总结,请参考: HashSet底层原理:(问了大几率 ...

  9. Java8线程池ThreadPoolExecutor底层原理及其源码解析

    小侃一下 日常开发中, 或许不会直接new线程或线程池, 但这些线程相关的基础或思想是非常重要的, 参考林迪效应; 就算没有直接用到, 可能间接也用到了类似的思想或原理, 例如tomcat, jett ...

随机推荐

  1. snprintf和sprintf区别分析

    目录[-] snprintf函数的返回值 snprintf函数的字符串缓冲 今天在项目中使用snprintf时遇到一个比较迷惑的问题,追根溯源了一下,在此对sprintf和snprintf进行一下对比 ...

  2. 使用IDEA+Gradle构建Spring5源码并调试(手把手教程全图解)

    一.前言   说一说我要写这篇文章的初衷吧,前段时间有小伙伴在微信群求教怎样构建spring源码,他在网上找了n个教程跟着后面花了两天时间都没构建好,正好我最近因工作原因从mac换成windows,开 ...

  3. SSM-框架搭建-tank后台学习系统

    一.前言 最近收到很多网友给我私信,学习软件开发有点吃力,不知道从何处开始学习,会点基础但是做不出来什么项目, 都想放弃了.我就回复道:当下互联网飞速发展,软件开发行业非常吃香而且前景相当不错.希望能 ...

  4. ODBC 常见数据源配置整理

    目录 1. 简介 1.1 ODBC和JDBC 1.2 ODBC配置工具 1.3 ODBC 数据源连接配置 2. MySQL 数据源配置 2.1 配置步骤 2.2 链接参数配置 3. SQLServer ...

  5. 从Spring Initializr开始

    出识springcloud我们这里需要建立两个项目 来感受下微服务 一.配置服务 1. Spring Initializr. 用idea自带的 Spring Initializr. 建立第一个项目 2 ...

  6. Maven的pom文件依赖提示 ojdbc6 Missing artifact,需要手动下载并导入maven参考

    eg: 需要 ojdbc6.jar 的下载地址 https://www.oracle.com/database/technologies/jdbcdriver-ucp-downloads.html c ...

  7. c语音学习笔记

    1.学习教程参考了杨光福 android jni Android视频<JNI> http://edu.csdn.net/course/detail/3235/54186?auto_star ...

  8. 如何运用Linux进行查看tomcat日志

    第一步:进入tomcat目录下的logs.cd home /tomcat/logs 第二步:运行并查看日志:tail -f catalina.out 第三步:想终止查看:ctrl +c退出 第四步:比 ...

  9. 使用 nuget server 的 API 来实现搜索安装 nuget 包

    使用 nuget server 的 API 来实现搜索安装 nuget 包 Intro nuget 现在几乎是 dotnet 开发不可缺少的一部分了,还没有用过 nuget 的就有点落后时代了,还不快 ...

  10. RS232/485通信方式 保存和加载时数据的处理

    RS232/485通信方式 数据以RS232/485方式通信时,以0xA5作为开始码,以0xAE作为结束码.在开始码和结束码之间的0xA5, 0xAA, 0xAE数据需要进行转码. PC端发送数据时将 ...