什么是CAS

CAS 即 compare and swap,比较并交换。

CAS是一种原子操作,同时 CAS 使用乐观锁机制。

J.U.C中的很多功能都是建立在 CAS 之上,各种原子类,其底层都用 CAS来实现原子操作。用来解决并发时的安全问题。

并发安全问题

举一个典型的例子i++

public class AddTest {
public volatile int i;
public void add() {
i++;
}
}

通过javap -c AddTest可以看到add 方法的字节码指令:

public void add();
Code:
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return

i++被拆分成了多个指令:

  1. 执行getfield拿到原始内存值;
  2. 执行iadd进行加 1 操作;
  3. 执行putfield写把累加后的值写回内存。

假设一种情况:

  • 线程 1 执行到iadd时,由于还没有执行putfield,这时候并不会刷新主内存区中的值。
  • 此时线程 2 进入开始运行,刚刚将主内存区的值拷贝到私有内存区。
  • 线程 1正好执行putfield,更新主内存区的值,那么此时线程 2 的副本就是旧的了。错误就出现了。

如何解决?

最简单的,在 add 方法加上 synchronized 。

public class AddTest {
public volatile int i;
public synchronized void add() {
i++;
}
}

虽然简单,并且解决了问题,但是性能表现并不好。

最优的解法应该是使用JDK自带的CAS方案,如上例子,使用AtomicInteger

public class AddIntTest {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}

底层原理

CAS 的原理并不复杂:

  • 三个参数,一个当前内存值 V、预期值 A、更新值 B
  • 当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true
  • 否则什么都不做,并返回 false

AtomicInteger 类分析,先来看看源码:

我这里的环境是Java11,如果是Java8这里一些内部的一些命名有些许不同。

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; /*
* This class intended to be implemented using VarHandles, but there
* are unresolved cyclic startup dependencies.
*/
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); private volatile int value; //...
}

Unsafe 类,该类对一般开发而言,少有用到。

Unsafe 类底层是用 C/C++ 实现的,所以它的方式都是被 native 关键字修饰过的。

它可以提供硬件级别的原子操作,如获取某个属性在内存中的位置、修改对象的字段值。

关键点:

  • AtomicInteger 类存储的值在 value 字段中,而value字段被volatile

  • 在静态代码块中,并且获取了 Unsafe 实例,获取了 value 字段在内存中的偏移量 VALUE

接下回到刚刚的例子:

如上,getAndIncrement() 方法底层利用 CAS 技术保证了并发安全。

public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}

getAndAddInt() 方法:

public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}

v 通过 getIntVolatile(o, offset)方法获取,其目的是获取 ooffset 偏移量的值,其中 o 就是 AtomicInteger 类存储的值,即valueoffset 内存偏移量的值,即 VALUE

重点weakCompareAndSetInt 就是实现 CAS 的核心方法

  • 如果 ov相等,就证明没有其他线程改变过这个变量,那么就把 v 值更新为 v + delta,其中 delta 是更新的增量值。
  • 反之 CAS 就一直采用自旋的方式继续进行操作,这一步也是一个原子操作。

分析:

  • 设定 AtomicInteger 的原始值为 A,线程 1线程 2 各自持有一份副本,值都是 A。
  1. 线程 1 通过getIntVolatile(o, offset)拿到 value 值 A,这时线程 1 被挂起。
  2. 线程 2 也通过getIntVolatile(o, offset)方法获取到 value 值 A,并执行weakCompareAndSetInt方法比较内存值也为 A,成功修改内存值为 B。
  3. 这时线程 1 恢复执行weakCompareAndSetInt方法比较,发现自己手里的值 A 和内存的值 B 不一致,说明该值已经被其它线程提前修改过了。
  4. 线程 1 重新执行getIntVolatile(o, offset)再次获取 value 值,因为变量 value 被 volatile 修饰,具有可见性,线程A继续执行weakCompareAndSetInt进行比较替换,直到成功

CAS需要注意的问题

使用限制

CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的,在Java中普通用户无法直接使用,只能借助atomic包下的原子类使用,灵活性受限。

但是CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS无能为力。

原子性也不一定能保证线程安全,如在Java中需要与volatile配合来保证线程安全。

ABA 问题

概念

CAS 有一个问题,举例子如下:

  • 线程 1 从内存位置 V 取出 A
  • 这时候线程 2 也从内存位置 V 取出 A
  • 此时线程 1 处于挂起状态,线程 2 将位置 V 的值改成 B,最后再改成 A
  • 这时候线程 1 再执行,发现位置 V 的值没有变化,符合期望继续执行。

此时虽然线程 1还是成功了,但是这并不符合我们真实的期望,等于线程 2狸猫换太子线程 1耍了。

这就是所谓的ABA问题

解决方案

引入原子引用,带版本号的原子操作。

把我们的每一次操作都带上一个版本号,这样就可以避免ABA问题的发生。既乐观锁的思想。

  • 内存中的值每发生一次变化,版本号都更新。

  • 在进行CAS操作时,比较内存中的值的同时,也会比较版本号,只有当二者都没有变化时,才能执行成功。

  • Java中的AtomicStampedReference类便是使用版本号来解决ABA问题的。

高竞争下的开销问题

  • 在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。

  • 针对这个问题的一个思路是引入退出机制,如重试次数超过一定阈值后失败退出。

  • 更重要的是避免在高竞争环境下使用乐观锁。


Java并发/多线程-CAS原理分析的更多相关文章

  1. java并发 - 自底向上的原理分析

    [TOC] 事先声明,我只是java并发的新手,这篇文章也只是我阅读<java并发编程的艺术>一书(内容主要涉及前3章)的一些总结和感悟.希望大家能多多讨论,对于错误的地方还请指出. 0. ...

  2. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

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

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

  4. java 并发多线程 锁的分类概念介绍 多线程下篇(二)

    接下来对锁的概念再次进行深入的介绍 之前反复的提到锁,通常的理解就是,锁---互斥---同步---阻塞 其实这是常用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案 抗战电影中,经常出现为了阻止日 ...

  5. JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析 http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balaba ...

  6. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  7. (6)Java数据结构-- 转:JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析  http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balab ...

  8. Java并发-volatile的原理及用法

    Java并发-volatile的原理及用法 volatile属性:可见性.保证有序性.不保证原子性.一.volatile可见性 在Java的内存中所有的变量都存在主内存中,每个线程有单独CPU缓存内存 ...

  9. Java NIO使用及原理分析 (四)

    在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...

随机推荐

  1. Typora快捷使用方式

    快捷使用: 1.一级标题 # + 空格 + 内容 2.六级标题 # + 空格 + 内容 3.有序序号 1. + 空格 + 内容 4.无序序号 -+ 空格.*+空格.++空格 5.代码块 ```pyth ...

  2. Eureka系列(二) 服务注册Server端具体实现

    服务注册 Server端流程   我们先看下面这张图片,这张图片简单描述了下我们EurekaClient 在调用EurekaServer 提供的服务注册Http接口,Server端实现接口执行的大致流 ...

  3. 抖音爬虫教程,python爬虫采集反爬策略

    一.爬虫与反爬简介 爬虫就是我们利用某种程序代替人工批量读取.获取网站上的资料信息.而反爬则是跟爬虫的对立面,是竭尽全力阻止非人为的采集网站信息,二者相生相克,水火不容,到目前为止大部分的网站都还是可 ...

  4. 【终极版】利用阿里云云解析API实现动态域名解析(ddns),搭建私有服务器【含可执行文件和源码】

    前言:懒人可以直接往下看,有代码和全部资源可以下载.此文章是先前文章的定时版,主要是添加了定时执行的功能,并且将代码中的配置项放置到了app.config文件中,方便不懂开发的朋友修改使用.未经许可请 ...

  5. WPF 中的相关样式

    <Image Name="icon" Width="40" Height="40"  Source="/Resources/ ...

  6. matplotlib学习日记(八)----完善统计图

    (一)再说legend() import matplotlib.pyplot as plt import numpy as np x = np.arange(0, 2.1, 0.1) y = np.p ...

  7. session在什么时候创建,以及session一致性问题

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/wowwilliam0/article/d ...

  8. eclips快捷键

    所谓"工欲善其事必先利其器",程序写多了,对于快捷键总有些特别的偏爱.在众多编辑器中,Eclipse算是用的比较多,也是最熟的. 最常用(也是最爱的:)) Ctrl+' :  自动 ...

  9. Java获取X509证书里的指纹(SHA-1)从pxf文件里面

    直接通过流去获取pxf后缀文件的内容,指纹通过X509才能获取.String keyStorefile = "pfx文件地址";String strPassword = " ...

  10. webservcie学习之webservice平台技术与开发

    webservice平台技术有哪些 XML+XSD,SOAP和WSDL就是构成WebService平台的三大技术. 1.XML+XSD WebService采用HTTP协议传输数据,采用XML格式封装 ...