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. Jenkins中agent的使用

    [前言] 很多小伙伴都已经会搭建Jenkins环境了,都想要用Jenkins来运行自动化接口,可我们的Jenkins在linux服务器上.服务器上默认的python包是2.6的这样不是很好,那么这边就 ...

  2. 不同类型数据库中LIKE语句使用

    不同数据库的LIKE语句使用略有差别,这里记录一下: Oracle数据库: SELECT *FROM userWHEREname LIKE CONCAT('%',#{name},'%')或SELECT ...

  3. 从外包公司运作方式看EJB工作原理

    从来没用过EJB,然后进了家公司需要用,没办法,硬着头皮学吧.以下是个人学习体会,觉不好的话也不要吐槽了. 关于EJB的工作原理,你可以想象为一家公司(EJB容器),外包型的(服务接口), 公司内部有 ...

  4. .net core 静态类获取appsettings

    注入获取 通过IConfiguration直接获取的方法官方文档里就有,可以直接看这里 如:appsettings.json { "Position": { "Title ...

  5. Redis系列(六):数据结构List双向链表LPUSH、LPOP、RPUSH、RPOP、LLEN命令

    1.介绍 redis中的list既实现了栈(先进后出)又实现了队列(先进先出) 1.示意图 2.各命令详解 LPUSH/RPUSH LPUSH: 从队列的左边入队一个或多个元素 将所有指定的值插入到存 ...

  6. Python实用笔记 (16)函数式编程——偏函数

    假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去: def int2(x, base=2): re ...

  7. 改变securecrt背景色

    下拉菜单中点击 Session Options--->

  8. Python3笔记004 - 2.1 python的语法特点

    第2章 python语言基础 python语法特点 保留字与标识符 变量 数据类型 运算符 输入和输出 2.1 python的语法特点 2.1.1 注释 注释的内容将被python解释器忽略,并不会在 ...

  9. fastjson到底做错了什么?为什么会被频繁爆出漏洞?

    GitHub 15.8k Star 的Java工程师成神之路,不来了解一下吗! GitHub 15.8k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 15.8k Star ...

  10. 苹果手机history.back()返回不刷新问题

    苹果手机,a页面打开b页面,b页面使用history.back(-1)返回a页面时,a页面不刷新,可在a页面添加以下代码: var isPageHide = false; window.addEven ...