CAS是什么?

比较并交换。

CAS示例

package com.chinda.java.audition;

import java.util.concurrent.atomic.AtomicInteger;

/**
* CAS示例
* 1. 什么是CAS
* CAS就是比较并交换
*
* @author Wang Chinda
* @date 2020/5/3
* @see
* @since 1.0
*/ public class CASDemo {
static AtomicInteger atomicInteger = new AtomicInteger(5); public static void main(String[] args) {
// 若主内存中的值是5, 替换成2020, 返回是否替换成功
System.out.println(atomicInteger.compareAndSet(5, 2020) + "\t current data: " + atomicInteger.get());
// 若主内存中的值是5, 替换成2048, 返回是否替换成功
System.out.println(atomicInteger.compareAndSet(5, 2048) + "\t current data: " + atomicInteger.get());
}
}

内存模型解析

CAS 底层原理

getAndIncrement源码解析

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
} private volatile int value; public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
}
  • Unsafe

Unsafe是CAS核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因此Java中CAS操作的执行操作依赖于Unsafe类的方法。

注意:Unsafe类中所有的方法都是native修饰的,也就是说Unsage类中的方法都直接调用操作系统底层资源执行相应任务。

  • valueOffset

表示该变量值在内存中的偏移量地址(内存地址),因为Unsafe就是根据内存偏移地址获取数据的。

public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
  • 变量value

变量value是用volatile修饰的,保证了多线程之间的内存可见性。

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取var1对象在var2地址的值, 即当前对象,当前时间,主内存中var2地址的值
var5 = this.getIntVolatile(var1, var2);
// 获取当前对象当,前时间,var2地址的值与var5比较,若是相同,说明主内存中的值没有被其余线程修改,将主内存中的值修改为var5+var4,返回true跳出循环;若是不同,继续获取主内存中的值,继续比较,直至相同,赋值,返回true,跳出循环为止。
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
}

var1:AtomicInteger对象本身。

var2:该对象值的引用地址。

var4:需要变动的值。

var5:是用var1,var2找出的主内存中真实的值。

用该对象当前的值与var5比较,如果相同,将主内存中的只修改为var5+var4,并且返回true,跳出循环;如果不同,继续取值再比较,直至相同,赋值,返回true为止。

多线程情况解析

假设线程A和线程B两个线程同时执行getAndIncrement操作。

  1. AtomicInteger的value属性的原始值设为3,即主内存中AtomicInteger的value值为3,根据JMM模型,线程A和线程B各自持有一份值为3的value副本分别存放在各自的工作内存中。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值为3,这时线程A被挂起。
  3. 线程B也通过getIntVolatile(var1, var2)拿到value值为3,此时线程B被没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B执行完成。
  4. 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己工作内存中的3和主内存中的数值4不一致,说明该值已经被其它线程抢先一步修改过,那么线程A本次修改失败,只能重新读取重新来过。
  5. 线程A重新获取value值,因为变量value被volatile修饰,所以其他线程对它修改,线程A总是可见的,线程A继续执行compareAndSwapInt进行比较替换,直到替换成功。

CAS 缺点

  • 循环时间长,开销大。
  • 只能保证一个共享变量的原子性。
  • 存在ABA问题。

ABA问题

CAS算法实现一个重要的前提是需要取出内存中某一时刻数据并在当下时刻比较并替换,那么在这个时间差会导致数据的变化。

比如说线程A从主内存中取出10,这时线程B也从主内存中取出10,并且线程B进行一些操作,将值改成了不为10的值,将这个值各种蹂躏,最后给改回10,这时线程A进行CAS操作发现主内存中的值仍然是10,然后进行替换,操作成功。尽管线程A的CAS操作成功,但不代表这个过程是没有问题的。

原子引用

package com.chinda.java.audition;

import java.util.concurrent.atomic.AtomicReference;

/**
* 原子引用
*
* @author Wang Chinda
* @date 2020/5/8
* @see
* @since 1.0
*/
public class AtomicRe { public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<User>();
User zs = new User("张三", 25);
User ls = new User("李四", 23);
atomicReference.set(zs);
System.out.println(atomicReference.compareAndSet(zs, ls) + "\t " + atomicReference.get());
System.out.println(atomicReference.compareAndSet(zs, ls) + "\t " + atomicReference.get());
}
} class User {
private String name;
private Integer age; public User() {
} public User(String name, Integer age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} @Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

解决ABA问题

原子引用上添加一种机制,添加版本号(类似时间戳)。

package com.chinda.java.audition;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference; /**
* ABA解决
*
* @author Wang Chinda
* @date 2020/5/8
* @see
* @since 1.0
*/
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<Integer>(100, 1); public static void main(String[] args) { System.out.println("--------------------产生ABA问题代码-----------------------");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start(); new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2020) + "\t " + atomicReference.get());
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--------------------解决ABA问题代码-----------------------");
new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本号: " + stamp);
try {
// 保证t4线程可以从主内存中获取第一版本数据
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(100, 101, stamp, stamp + 1);
stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第二次版本号: " + stamp);
stampedReference.compareAndSet(101, 100, stamp, stamp + 1);
stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第三次版本号: " + stamp);
}, "t3").start(); new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本号: " + stamp);
try {
// 保证t3线程完成一次ABA操作
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + stampedReference.compareAndSet(100, 2020, stamp, stamp + 1) + "\t 版本号: " + stamp); }, "t4").start();
}
}

JUC(二):CAS及ABA的更多相关文章

  1. java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)

    这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...

  2. 沉淀再出发:java中的CAS和ABA问题整理

    沉淀再出发:java中的CAS和ABA问题整理 一.前言 在多并发程序设计之中,我们不得不面对并发.互斥.竞争.死锁.资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切 ...

  3. CAS及其ABA问题

    CAS.volatile是JUC包实现同步的基础.Synchronized下的偏向锁.轻量级锁的获取.释放,lock机制下锁的获取.释放,获取失败后线程的入队等操作都是CAS操作锁标志位.state. ...

  4. Java高性能编程之CAS与ABA及解决方法

    Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compar ...

  5. CAS的ABA问题详解

    CAS的ABA问题详解 ABA问题 在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下 1.线程1,期望值为A ...

  6. CAS 和 ABA 问题

    CAS简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制. CAS 它是一条CPU并发原语.操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...

  7. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  8. 谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题

    于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用At ...

  9. JUC(10)深入理解CAS和ABA

    文章目录 1.CAS 2.原子引用解决ABA问题,版本号.修改后,可以看到 1.CAS package com.cas; import java.util.concurrent.atomic.Atom ...

随机推荐

  1. c++11-17 模板核心知识(八)—— enable_if<>与SFINAE

    引子 使用enable_if<>禁用模板 enable_if<>实例 使用Concepts简化enable_if<> SFINAE (Substitution Fa ...

  2. Fastjson远程代码执行漏洞复现

    fastjson漏洞简介 Fastjson是一个Java库,可用于将Java对象转换为其JSON表示形式.它还可以用于将JSON字符串转换为等效的Java对象,fastjson爆出多个反序列化远程命令 ...

  3. phpstudy搭建网站只能访问首页,其他路由访问404

    今天博主遇到了一个很奇葩的问题,电脑下载了一个phpstudy搭建网站,框架用的是tp,但是除了输入域名能访问,其他页面都访问不了 经过博主的疯狂问大佬,以及百度,终于解决了这个问题 这次出现问题的原 ...

  4. jq根据id替换修改或添加css属性

    jq方法 $("#id").css("display","none"); js方法 document.getElementById(&quo ...

  5. 看阿里P7讲MyBatis:从MyBatis的理解以及配置和实现全帮你搞懂

    前言 MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL.存储过程以及高级映`射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结 ...

  6. [TroubleShootting]Zabbix数据采集出现断点的问题

    背景 最近发现公司的Zabbix监控大屏上的监控图经常出现数据断点的现象,主要集中在一些自定义的监控项数据上,如下图: 原因 查看Zabbix Server日志以及zabbix官方手册后,分析可能原因 ...

  7. MySQL数据更新

    MySQL数据更新: 导读: 该练习是本人课程学习进行整理的,若有不对,欢迎指出! 该练习没有配套的sql文件,如果需要可以看之前的文章有student表等(MySQL查询练习); 这是最后一部分练习 ...

  8. SpringBoot中JPA的学习

    SpringBoot中JPA的学习 准备环境和项目配置 写一下学习JPA的过程,主要是结合之前SpringBoot + Vue的项目和网上的博客学习一下. 首先,需要配置一下maven文件,有这么两个 ...

  9. web端项目如何测试

    1.是否支持各种网络 2.网络如果演示能否正常加载 3.加载时断网还能加载出来么 4.浏览时断网页面是否保持 5.是否兼容各种不同的浏览器 6.不同的浏览器加载出的页面是否一致 7.页面效果如何 8. ...

  10. SkyWalking —— 分布式应用监控与链路追踪

    SkyWalking 是一个应用性能监控系统,特别为微服务.云原生和基于容器(Docker, Kubernetes, Mesos)体系结构而设计.除了应用指标监控以外,它还能对分布式调用链路进行追踪. ...