一:CAS简介

CAS:Compare And Swap(字面意思是比较与交换),JUC包中大量使用到了CAS,比如我们的atomic包下的原子类就是基于CAS来实现。区别于悲观锁synchronized,CAS是乐观锁的一种实现,在某些场合使用它可以提高我们的并发性能。

在CAS中,主要是涉及到三个操作数,所期盼的旧值、当前工作内存中的值、要更新的值,仅当所期盼的旧值等于当前值时,才会去更新新值。

二:CAS举例

比如当如下场景,由于i++是个复合操作,读取、自增、赋值三步操作,因此在多线程条件下我们需要保证i++操作的安全

public class CASTest {
int i = 0; public void increment() {
i++;
}
}

解决办法有通过使用synchronized来解决,synchronized解决了并发编程的原子性,可见性,有序性。

public class CASTest {
int i = 0; public synchronized void increment() {
i++;
}
}

但synchronized毕竟是悲观锁,尽管它后续进行了若干优化,引入了锁的膨胀升级措施,但是还是存在膨胀为重量级锁而导致阻塞问题,因此,我们可以使用基于CAS实现的原子类AtomicInteger来保证其原子性

public class CASTest {
AtomicInteger i = new AtomicInteger(0);
public static void increment() {
//自增并返回新值
i.incrementAndGet();
}
}

三:CAS原理分析

atomic包下的原子类就是基于CAS实现的,我们拿AtomicInteger来分析下CAS.

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; // CAS操作是基于一个Unsafe类,Unsafe类是整个Concurrent包的基础,里面所有的函数都是native的
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); }
}
//底层采用volatile修饰值,保证其可见性和有序性
private volatile int value;

从AtomicInteger定义的相关属性来看,其内部的操作都是基于Unsafe类,因为在Java中,我们并不能直接操作内存,但是Java还是开放了一个Unsafe类来给我们进行操作,顾名思义,Unsafe,是不安全的,因此要谨慎使用。

其内部定义的值是用volatiel进行修饰的,volatile可以保证有序性和可见性,具体为什么可以保证就不在此阐述了。

再来看看其几个核心的API

//以原子方式将值设置为给定的新值 expect:期望值 update:旧值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//以原子方式将当前值+1,返回期望值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
} //以原子方式将当前值-1,返回期望值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}

关于其源码还是很少的,基本都是基于Unsafe类进行实现的。

先来看看compareAndSet方法,其调用的是Unsafe的compareAndSwapInt方法,当工作内存中的值与所期盼的旧值不相同的时候,会更新失败,举例说明:

public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println("更新结果:"+atomicInteger.compareAndSet(2020, 2021));
System.out.println("当前值为:"+atomicInteger.get()); //自增加一
atomicInteger.getAndIncrement(); System.out.println("更新结果:"+atomicInteger.compareAndSet(2020, 2021));
System.out.println("当前值为:"+atomicInteger.get());
}
}

在来看看incrementAndGet方法,其调用的是unsafe.getAndAddInt方法,其就相当于是自旋锁的实现,当所期盼的旧值与新值相同时才更新成功,否则就进行自旋操作直到更新成功为止。

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
}

四:CAS缺点分析

CAS的优点很明显,基于乐观锁的思想,提高了并发情况下的性能,缺点主要是ABA问题、自旋时间过长导致CPU占有率过高、只能保证一个共享变量的原子性。

ABA问题

就是一个值由A变为B,在由B变为A,使用CAS操作无法感知到该种情况下出现的变化,带来的后果很严重,比如银行内部员工,从系统挪走一百万,之后还了回来,系统感知不到岂不是要出事。模拟下出现ABA问题:

   public class ABA {
private static AtomicInteger atomicInteger = new AtomicInteger(0); public static void main(String[] args) {
//线程t1实现0->1->0
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
atomicInteger.compareAndSet(0,1);
atomicInteger.compareAndSet(1,0);
}
},"t1"); //线程t2实现0->100
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟狸猫换太子行为
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新结果:"+atomicInteger.compareAndSet(0, 100));
}
}); t1.start();
t2.start();
}
}

运行结果是:true

解决ABA可以使每一次修改都带上时间戳,以记录版本号的形式来使的CAS感知到这种狸猫换太子的操作。Java提供了AtomicStampedReference类来解决,该类除了指定旧值与期盼值,还要指定旧的版本号与期盼的版本号

    public boolean compareAndSet(V   expectedReference, V   newReference, int expectedStamp, int newStamp) {
Pair<V> current = pair;
return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp ==current.stamp) || casPair(current, Pair.of(newReference, newStamp)));
}
public class ABA_Test {

    // 初始值100,版本号1
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1); public static void main(String[] args) throws InterruptedException {
// AtomicStampedReference实现
Thread tsf1 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 让 tsf2先获取stamp,导致预期时间戳不一致
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1
atomicStampedReference.compareAndSet(100, 110, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
atomicStampedReference.compareAndSet(110, 100, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
}
}); Thread tsf2 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = atomicStampedReference.getStamp(); try {
TimeUnit.SECONDS.sleep(2); // 线程tsf1执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
"AtomicStampedReference:" + atomicStampedReference.compareAndSet(100, 120, stamp, stamp + 1));
}
}); tsf1.start();
tsf2.start();
}
}

运行结果:

自旋次数过长

CAS是基于乐观锁的思想实现的,当频繁出现当前值与所旧预期值不相等的情况,会导致频繁的自旋而使得浪费CPU资源。

只能保证单个共享变量的原子性

单纯对共享变量进行CAS操作,只能保证单个,无法使多个共享变量同时进行原子操作。

参考资料

狂神说Java:www.bilibili.com/video/BV1B7…
CAS机制及AtomicInteger源码分析:juejin.im/post/5e2182…

 

浅析CAS与AtomicInteger原子类的更多相关文章

  1. 使用AtomicInteger原子类代替i++线程安全操作

    Java中自增自减操作不具原子性,在多线程环境下是线程不安全的,可以使用使用AtomicInteger原子类代替i++,i--操作完成多线程线程安全操作. 下面是等于i++多线程的自增操作代码: pu ...

  2. Netty的并发编程实践3:CAS指令和原子类

    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁.随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为 ...

  3. CAS基础和原子类

    基于CAS实现的AtomicInteger. AtomicLong. AtomicReference. AtomicBoolean也被称为乐观锁. CAS的语义是“我认为V的值应该为A,如果是,那么将 ...

  4. java 多线程12 : 无锁 实现CAS原子性操作----原子类

    由于java 多线程11:volatile关键字该文讲道可以使用不带锁的情况也就是无锁使变量变成可见,这里就理解下如何在无锁的情况对线程变量进行CAS原子性及可见性操作 我们知道,在并发的环境下,要实 ...

  5. Java多线程系列——原子类的实现(CAS算法)

    1.什么是CAS? CAS:Compare and Swap,即比较再交换. jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronou ...

  6. Atomic原子类

    Atomic原子类 Atomic原子类位于并发包java.util.concurrent下的java.util.concurrent.Atomic中. 1. 原子更新基本类型类 使用原子方式更新基本数 ...

  7. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  8. 2.3.5使用原子类进行i++操作

    除了在i++操作时使用synchronized关键字实现同步外,还可以使用AtomicInteger原子类进行实现 原子操作时不可分割的整体,没有其他线程能够中断或检查正在原子操作的变量,一个原子类型 ...

  9. java 线程 原子类相关操作演示样例 thinking in java4 文件夹21.3.4

    java 线程  原子类相关操作演示样例 package org.rui.thread.volatiles; import java.util.Timer; import java.util.Time ...

随机推荐

  1. 【科创人独家】PingCAP黄东旭:想告诉图灵这个世界现在的样子

    创业是投己所好 科创人:作为技术圈内著名艺术青年,哪个瞬间会让您更开心,完成一段优美的代码或者乐谱?还是得到来自外界的欢呼与掌声? 黄东旭:在创业之前的很长一段时间里,完成一段代码.写完一首好曲子那一 ...

  2. 证明与计算(7): 有限状态机(Finite State Machine)

    什么是有限状态机(Finite State Machine)? 什么是确定性有限状态机(deterministic finite automaton, DFA )? 什么是非确定性有限状态机(nond ...

  3. MySQL数据库参数调优方法

    怎么配置MySQL服务器,但考虑到服务器硬件配置的不同,具体应用的差别,那些文章的做法只能作为初步设置参考,我们需要根据自己的情况进行配置优化,好的做法是MySQL服务器稳定运行了一段时间后运行,根据 ...

  4. 使用nginx访问FastDFS

    文中所有~~~均为同一个自定义文件夹名字,一般使用项目名称 2.1.为什么需要用Nginx访问? FastDFS通过Tracker服务器,将文件放在Storage服务器存储,但是同组存储服务器之间需要 ...

  5. 关于dll劫持我的奇思妙想(一)

    0x00  前言 前段时间在研究着windows底层的一些东西,发现这个dll劫持一直没有做过,根据倾旋师傅的视频和文章做了一系列的研究,然后就突发来了兴致研究一些dll劫持提权. 0x01   了解 ...

  6. Sql练习201908210951

    表结构: create table SalePlan ( PlanId ,) primary key, GoodsId int, PlanPrice decimal(,) ); go create t ...

  7. java应用诊断和在线debug利器bistoury介绍与在K8S环境使用

    Bistoury介绍 Bistoury 是去哪儿网开源的一个对应用透明,无侵入的java应用诊断工具,用于提升开发人员的诊断效率和能力,可以让开发人员无需登录机器或修改系统,就可以从日志.内存.线程. ...

  8. chrome浏览器的json格式化插件

    JSON-Handle   下载地址:         http://jsonhandle.sinaapp.com/ 插件下载后,在浏览器输入:chrome://extensions/ 将下载后的文件 ...

  9. 使用systemctl工具

                           使用systemctl工具 8.1问题 本例要求掌握systemctl控制工具的基本操作,完成下列任务: 重启httpd.crond.bluetooth服 ...

  10. 配置并验证Split分离解析

                                                             配置并验证Split分离解析 案例1:配置并验证Split分离解析 案例2:查看进程信 ...