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. Android 滑动删除控件推荐

    implementation 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0' <?xml version="1.0" enc ...

  2. javascript——什么是解释型语言?

    摘要:<JavaScript基础与案例开发详解>(张孝祥,徐明华)第2章JavaScript环境,本章力求让读者了解JavaScript的开发环境.运行环境,和开发中会遇见的一些问题,做好 ...

  3. 【建议收藏】一份阿里大牛花了三天整理出来的XML学习笔记,写的非常详细

    1. 什么是XML? XML 指可扩展标记语言(EXtensible Markup Language)XML 是一种标记语言,很类似 HTMLXML 的设计宗旨是传输数据,而非显示数据XML 标签没有 ...

  4. 史上最全!2020面试阿里,字节跳动90%被问到的JVM面试题(附答案)

    前言:最近老是收到小伙伴的私信问我能不能帮忙整理出一份JVM相关的面试题出来,说自己在大厂去面试的时候这一块问的是特别多的,每次自己学的时候每次都学不到重点去.这不他来了,一份详细的JVM面试真题给大 ...

  5. 「CSP-S 2019」划分

    description loj 3212 solution 首先容易想到\(n^3\)DP,即令\(f_{i,j}\)表示前\(i\)个数的划分,其中最后一段是从\(j\)开始时的答案 于是有 \[f ...

  6. python应用(1):安装与使用

    程序员的基本工作是写程序,而写程序要用到编程语言,编程语言可以分为编译型语言跟解释型语言. 编译型语言,就是在执行代码之前,先把源代码编译(加链接)成另一种形式的代码,比如目标代码,或字节码,这种代码 ...

  7. 工作中用到的redis操作

    del exists 1.字符串 set,get 2.列表 lRange lRem lPush rPush 3.有序列表 zadd zrem zscore 4.hash hset hget hdel

  8. js 实现textarea剩余字数统计

    1 针对textarea剩余字数统计 2 <div class="fankui-textarea"> 3 <span>留言:</span> &l ...

  9. mysql幻读问题

    转载:https://blog.csdn.net/u013067756/article/details/90722490 关于间隙锁:https://blog.csdn.net/sinat_27143 ...

  10. C#中的WinForm问题——使用滚动条时页面闪烁及重影问题

    当使用鼠标进行滚动查看页面时,由于页面会频繁刷新,如果页面中控件较多会导致页面出现闪烁.重影等问题,如下图所示: 在网上搜索过该问题,大部分都说使用双缓冲可以解决此类问题,即通过设置DoubleBuf ...