什么是CAS?

  CAS是Compare And Swap的简称。在Java中有很多实现,比如compareAndSwapObject()方法,或者compareAndSwapInt()方法等。多用在包java.util.concurrent.atomic下的类中来实现原子性的操作。这里主要是结合compareAndSwapInt()方法介绍一下CAS的核心思想以及一些问题。

什么是原子性?

  对于原子,相信学过化学的都知道,化学反应到最后不可分割的是原子。如果把我们的系统各个线程比作各种各样的化学方程式的话,其中有一部分我们就希望无论系统怎么运行,这部分都是像原子一样不能再被分割。前面说过synchronized和CAS可以实现原子性,而volatile不可以,从使用的方法上我们也可以看出,synchronized修饰的部分都是代码块或方法,也就是范围大,可以理解里面的部分都是原子的组成部分,同理CAS实际上也是一个方法,而volatile只是修饰一个变量。要实现原子性也很简单,只需要保证每次只有一个线程来运行这里面的代码即可,像我们刚接触Java时只写了主线程,那么主线程中的代码就是原子性的。上次介绍了synchronized关键字原理,现在就看看CAS在底层到底是怎么实现的。

++运算符和getAndIncreament()方法的线程安全性比较

  在平常相信每个人都接触过for循环

for(int i=0;i<10;i++);

  上面这段代码大家肯定都敲过很多遍。对于i++运算,其实表面看起来是i进行自增加,但其实在底层i++操作是分三步:

    1、从主存中取出i的值

    2、i进行加一操作

    3、把值写回主存

  这三步操作还是i是被volatile修饰的情况下才会进行,而平常更多的是直接操作线程副本里的i。当i是共享变量时在多个线程的情况下就会出现错误。比如线程A修改了i的值却没有写到内存,其他线程拿i的旧值进行操作,或者是线程A和线程B同时进行上面三步,那么原本是要加两次的,实际上就只加了一次。所以这是不安全的。而如果我们使用AtomicInteger类型中getAndIncreament()方法就是线程安全的,下面来分析一下这个方法为什么线程安全

public class AtomicInteger extends Number implements java.io.Serializable {
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
}

  我们看见这个方法中调用了unsafe.getAndAddInt方法并且传了三个参数,这三个参数分别是

    this:当前的AtomicInteger类的对象。

    valueOffset:可以理解为value在内存中的地址。也就是要从内存中获取最新的value值。

    1:这个就很好理解了,就是每次需要增加的数,也就是自增1。  

  然后进入unsafe.getAndAddInt方法中

public final class Unsafe {
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;
}
}

  说一下里面的大概实现的功能。首先定义了var5,var5是通过getIntVolatile方法获取当前内存中AtomicInteger当前对象的最新值,然后比较当前对象的值,也就是var1中的value属性值和我重新在主内存中拿的值var5对应的value值是否一样,一样才会进行加一操作,并且退出while循环,最后返回var5。如果不一样就会一直从底层中去取,直到取的是和当前对象的值一样了才会执行后面的操作。现在看到这其实已经发现很多CAS存在的问题了。

  1、ABA问题

    上面说的比较当前值和内存中的值一样怎么即可进行下次操作。那么如果有其他线程在改过这个变量的值之后,又因为某些操作而把变量的值改回来,这时候比较的值一样了,CAS就会进行相应的操作。通俗一点说就是现在线程1需要拿共享变量的A值进行加一操作,而线程2把共享变量的值改成B值,之后又把这个变量改回A值,那么在同一时刻实际上有两个线程操作了这个值,所以破坏了原子性。这个问题解决思路就是加上版本号,对这个变量改过一次版本号就更新一次。通过版本号来记录变量的改动次数。在JDK1.5后Atomic包中就提供了一个类AtomicStampedReference来解决ABA问题。这里说一下compareAndSet方法具体的步骤是首先检查引用是否等于预期引用,也就是在内存中拿的值是否等于预期操作的值,然后检查当前的标志是否等于预期的标志,这里可以理解为版本号。

  2、循环时间开销大

    如果从内存中拿到的值一直和预期的值不相等,那么就会一直循环一直拿。这样会给CPU带来很大的消耗,这里稍微提一下,JVM支持处理器提供的pause指令时,这个问题才会得到一定的改善。

  3、原子性不互相兼容

    这个其实是说AtomicInteger类下的单独一个方法是原子性的,但是两个方法之间放在一起操作这个对象就不是原子性的,或者对多个共享变量操作时,CAS也无法保证操作是原子性。这个可以通过锁来实现(也就是需要使用一个对象下的多个方法一起进行操作的时候使用)或者通过AtomicReference类来保证引用对象之间的原子性(内部是通过把多个变量放在一个对象里来进行CAS操作)

总结

  其实说到这,CAS只是其中的一个方法,而Atomic包下的类才是我们使用的主角,只不过底层在保证原子性的时候使用的是CAS方法+volatile变量,volatile变量是类中用来修饰value的,所以value会在每次操作的时候都会和主内存进行交互。CAS负责操作这个value值。CAS具体的实现是在用C++代码编写的,这里就没继续去看对应的源代码。若需要学习,完全可以去看看底层如何通过C++来实现的。

Java并发——CAS的更多相关文章

  1. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

  2. Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...

  3. Java并发(十二):CAS Unsafe Atomic

    一.Unsafe Java无法直接访问底层操作系统,而是通过本地(native)方法来访问.不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作. 这个类尽管 ...

  4. Java 并发(一) --- CAS

    CAS 原理 先来看看下面的代码是否可以输出预期的值.开启了两个线程,是否会输出200 呢 结果由于并发的原因,结果会小于或等于200 , 原因出现在 count++; 由于这一行代码存在三个操作: ...

  5. Java并发编程系列-(3) 原子操作与CAS

    3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...

  6. Java并发--Java中的CAS操作和实现原理

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

  7. JAVA并发编程: CAS和AQS

       版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/u010862794/article/details/72892300 说起JAVA并发编程,就不得不聊 ...

  8. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  9. Java并发编程之CAS第一篇-什么是CAS

    Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...

随机推荐

  1. 在C# 中 如何限制在文本框(textBox)中输入的类型为正整数

    在文本框的 KeyPress 事件中写下这些代码就可以保证是正整数了 private void textBox1_KeyPress(object sender, KeyPressEventArgs e ...

  2. Spring使用 --- 基本概念(二):AOP,面向方面编程

    Table of Contents 什么是面向方面编程 怎样使用 什么时候使用 好处 本文讲述sprint的第二个基本概念: AOP,即面向方面编程 什么是面向方面编程 软件项目中,日志系统等服务系统 ...

  3. [CVPR 2017] Semantic Autoencoder for Zero-Shot Learning论文笔记

    http://openaccess.thecvf.com/content_cvpr_2017/papers/Kodirov_Semantic_Autoencoder_for_CVPR_2017_pap ...

  4. 关于office在卸载了某一应用之后无法试图使用的功能所在的网络位置

    我出现这个问题是在卸载了某一个微软的办公软件之后,所有的办公软件都会产生这个问题. 处理的方法是将之前的安装包解压,然后找到所出现的msi文件,点击确定就ok了. 所以说,安装文件最好还是放在一个地方 ...

  5. BootStrap 常用控件总结

    下拉选择Select2:http://ivaynberg.github.io/select2/index.html 文件上传bootstrap-fileinput:https://github.com ...

  6. java反射获取类的类名、属性名、属性类型、方法、执行方法、构造函数

    public class Demo02 { @SuppressWarnings("all") public static void main(String[] args) thro ...

  7. SQL关于IN和EXISTS的用法和区别的比较

    1.exist,not exist一般都是与子查询一起使用. In可以与子查询一起使用,也可以直接in (a,b.....).2.exist会针对子查询的表使用索引. not exist会对主子查询都 ...

  8. SSM-SpringMVC-02:SpringMVC最简单的小案例

     ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 咱们这个案例做什么? 就是用处理器代替Servlet处理请求 开发步骤: 1.引入jar包 <!--单 ...

  9. Linux时间子系统之二:表示时间的单位和结构

    人们习惯用于表示时间的方法是:年.月.日.时.分.秒.毫秒.星期等等,但是在内核中,为了软件逻辑和代码的方便性,它使用了一些不同的时间表示方法,并为这些表示方法定义了相应的变量和数据结构,本节的内容就 ...

  10. BackBox错误,无法获得锁...资源暂时不可用...无法锁定管理目录

    今天准备给BackBox安装leafpad时,输入 sudo apt install leafpad 后出现了如下的错误提示: E: 无法获得锁 /var/lib/dpkg/lock - open ( ...