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. Elasticsearch搜索资料汇总

    Elasticsearch 简介 Elasticsearch(ES)是一个基于Lucene 构建的开源分布式搜索分析引擎,可以近实时的索引.检索数据.具备高可靠.易使用.社区活跃等特点,在全文检索.日 ...

  2. 蓝桥杯——测试次数·摔手机(2018JavaB组第4题,17分)

    x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机. 各大厂商也就纷纷推出各种耐摔型手机.x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通. x ...

  3. go特性-defer

    1:后定义的defer先执行(可以理解为栈的方式) // 222 // 111 func Test1(t *testing.T) { defer fmt.Println("111" ...

  4. eNSP VLAN划分基础配置及Access接口

    交换机内实现VLAN通信拓扑图: 一.修改主机名并保存 1.进入系统视图模式(配置模式) <Huawei>system-view 2.sysname命令修改主机名为yanyuda [Hua ...

  5. JZOJ8月6日提高组反思

    JZOJ8月6日提高组反思 又是愉快的没落的一天 被2020&2018暴打day2 一堆人AK-- T1 看到这个\(m\)只有100 就坚定了我打暴力的信心 离散化加暴力匹配 原本就想\(3 ...

  6. Jmeter(三十一) - 从入门到精通 - Jmeter Http协议录制脚本工具-Badboy4(详解教程)

    1.简介 上一篇文章中宏哥给小伙伴或童鞋们介绍讲解了手动添加Variable list的值,而实际工作中Badboy为我们提供了Variable setter工具,让我们不再使用哪一种比较笨拙的方法了 ...

  7. charles功能(一)修改request请求参数

    1.接口处 鼠标右击,选择breakpoints(允许本接口使用breakpionts功能) 2.开始设置断点值 3.然后修改这一条 4.然后执行 5.最终结果

  8. springmvc<二> 一些配置

    1.1.3. Web MVC Config    1.1.2中的解析器可以自定义实现,DispatcherServlet检查每个特殊bean的WebApplicationContext,如果没有匹配的 ...

  9. PyQt转换显示Python-OpenCV图像实现图形化界面的视频播放

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 在Python-OpenCV中显示图像时调用的是一个单独的窗口,有时我们需要将这些图像显示在PyQt的图形化界面上,这样就可以将整个图像显示与PyQ ...

  10. PyQt(Python+Qt)学习随笔:QSlider滑动条部件功能简介

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.概述 Designer输入部件中的Horizo ...