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. [web安全原理]PHP反序列化漏洞

    前言 这几天一直在关注新管状病毒,从微博到各大公众号朋友圈了解感觉挺严重的看微博感觉特别严重看官方说法感觉还行那就取中间的吧 自己要会对这个东西要有理性的判断.关注了好两天所以耽搁了学习emmm 希望 ...

  2. beef抓包简析

    搭建完了beef就想简答的抓下包分析下 这是第一个包,追踪它 返回demo页面,并发现其中的脚本 window.location.protocol表示协议http, window.location.h ...

  3. vue路由参数的获取、添加和替换

    获取路由参数 getUrlKey(name){//获取url 参数 return decodeURIComponent((new RegExp('[?|&]'+name+'='+'([^&am ...

  4. 会声会影使用教程:剪辑Vlogo短视频

    随着抖音.快手等视频分享软件的兴起,很多人已经开始尝试制作短视频分享.那么,对于视频制作新手来说,短视频的制作难度大吗?其实,只要选对了视频制作软件,视频制作将会变得相当简单. 在众多视频制作软件中, ...

  5. 实时检测微信域名防红拦截检测API系统,最新腾讯域名屏蔽检测官方接口

    最近手里有个项目需要检测域名在微信里是否可以打开,如果被微信拦截,则需要进行下一步操作,所以需要判断域名的状态,但是微信官方并没有提供相关查询的方法,最后在网上找到了这个接口地址,分享给有需要的朋友. ...

  6. thinkphp3.2 添加自定义类似__ROOT__的变量

    1 thinkphp3.2 添加自定义类似__ROOT__的变量 2 3 在config.php文件中 4 return array( 5 '' => '', 6 'TMPL_PARSE_STR ...

  7. Mac 上超好用的代码对比工具 beyond compare,对比json差异

    导读 昨天下午,公司业务跑不通,然后开发组长让架构师联系我,给我发一个json和部署到dev上的微服务url,让我去测试下,将发来的json放到json.cn上愣是解析不出来,我就用之前的json请求 ...

  8. 网络管理监视很重要!学编程的你知道哪些不错的网络监控工具?2020 最好的Linux网络监控工具分享给你

    以下文章来源于新钛云服 翻译:侯明明 前言 虽然这个清单包含开源的和闭源的产品,但它着重于介绍基于 Linux 的网络监控工具, 少数常用工具只能在 Windows,Pandora 或其他系统上运行, ...

  9. Java复数的定义与描述

    1 //4.复数的定义与描述 2 package test; 3 4 import java.util.Scanner; 5 6 public class complex {//复数类 7 doubl ...

  10. 创建topic

    sh kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic ...