JUC(二):CAS及ABA
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操作。
- AtomicInteger的value属性的原始值设为3,即主内存中AtomicInteger的value值为3,根据JMM模型,线程A和线程B各自持有一份值为3的value副本分别存放在各自的工作内存中。
- 线程A通过getIntVolatile(var1, var2)拿到value值为3,这时线程A被挂起。
- 线程B也通过getIntVolatile(var1, var2)拿到value值为3,此时线程B被没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B执行完成。
- 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己工作内存中的3和主内存中的数值4不一致,说明该值已经被其它线程抢先一步修改过,那么线程A本次修改失败,只能重新读取重新来过。
- 线程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的更多相关文章
- java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)
这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...
- 沉淀再出发:java中的CAS和ABA问题整理
沉淀再出发:java中的CAS和ABA问题整理 一.前言 在多并发程序设计之中,我们不得不面对并发.互斥.竞争.死锁.资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切 ...
- CAS及其ABA问题
CAS.volatile是JUC包实现同步的基础.Synchronized下的偏向锁.轻量级锁的获取.释放,lock机制下锁的获取.释放,获取失败后线程的入队等操作都是CAS操作锁标志位.state. ...
- Java高性能编程之CAS与ABA及解决方法
Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compar ...
- CAS的ABA问题详解
CAS的ABA问题详解 ABA问题 在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下 1.线程1,期望值为A ...
- CAS 和 ABA 问题
CAS简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制. CAS 它是一条CPU并发原语.操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...
- Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
- 谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题
于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用At ...
- JUC(10)深入理解CAS和ABA
文章目录 1.CAS 2.原子引用解决ABA问题,版本号.修改后,可以看到 1.CAS package com.cas; import java.util.concurrent.atomic.Atom ...
随机推荐
- Single Depth peeling 顺序无关渲染(OIT)
什么是顺序无关渲染 在3D渲染中,物体的渲染是按一定的顺序渲染的,这也就可能导致半透明的物体先于不透明的物体渲染,结果就是可能出现半透明物体后的物体由于深度遮挡而没有渲染出来.对于这种情况通常会先渲染 ...
- python-基础入门-5(模块、类和对象)
模块 模块用import来调用,例如 from sys import argv 调用sys中argv模块 在模块里有多个def的函数 import调用全部或其中一个 类和对象 下面定义了一个类 1 c ...
- 追踪聚光特效怎么实现,有Vegas就够了
舞台聚光灯大家一定都不陌生,在电视上某些颁奖活动里,主持人的进场一定伴随着舞台灯光的聚光效果.随着主持人的移动,灯光也随之移动.这里的舞台灯光就起到了一个追踪聚光的效果. Vegas Pro 16 增 ...
- CentOS 防火墙常用命令
1.查看防火墙状态: firewall-cmd --state 2.启动防火墙 systemctl start firewalld 3.关闭防火墙 systemctl stop firewalld 4 ...
- Pytest自动化测试 - 必知必会的一些插件
Pytest拥有丰富的插件架构,超过800个以上的外部插件和活跃的社区,在PyPI项目中以" pytest- *"为标识. 本篇将列举github标星超过两百的一些插件进行实战演示 ...
- P2592 [ZJOI2008]生日聚会
容易发现已经结束掉的一个子串只要合法就对后面没有影响,所以可以令 \(f_{i,j,p,q}\) 表示前 \(i+j\) 个人有 \(i\) 个男孩,\(j\) 个女孩,所有后缀中男孩最多比女孩多 \ ...
- Java基础教程——使用Eclipse快速编写Java输入输出代码
Eclipse安装 IDE:Integrated Development Environment,集成开发环境.好比是全自动洗衣机. 此处使用[eclipse-jee-4.6-neon-3-win32 ...
- vue前端静态页面Github Pages线上预览实现
一.前期准备之项目编译 此处记录如何解决vue2.0 打包之后,打开index.html出现空白页的问题,附上@参考地址 打包之前修改三个文件 第一步,找到build文件,在webpack.prod. ...
- 【NOIP2017提高A组模拟9.17】信仰是为了虚无之人
[NOIP2017提高A组模拟9.17]信仰是为了虚无之人 Description Input Output Sample Input 3 3 0 1 1 7 1 1 6 1 3 2 Sample O ...
- (十八)面向流水线的设计:CPU的一心多用
一.单指令周期 由前可知,一条CPU指令的执行有三个步骤:指令读取.指令译码.指令执行.由于这个过程受CPU时钟的控制,如果我们将这个过程安排在一个CPU时钟周期内执行,这种设计思路就叫单 ...