什么是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. windows下安装mysql-5.7.11-winx64

    1.解压.   2.将『D:\Program Files\mysql-5.7.11-winx64\bin』加入系统环境变量.   3.修改my-default.ini.   4.初始化data目录,在 ...

  2. Springboot 框架学习

    Springboot 框架学习 前言 Spring Boot是Spring 官方的顶级项目之一,她的其他小伙伴还有Spring Cloud.Spring Framework.Spring Data等等 ...

  3. [ 搭建Redis本地服务器实践系列一 ] :图解CentOS7安装Redis

    上一章 [ 搭建Redis本地服务器实践系列 ] :序言 作为开场白介绍了下为什么要写这个系列,从这个章节我们就开始真正的进入正题,开始搭建我们本地的Redis服务器.那么关于Redis的基本概念,什 ...

  4. VMware Workstation 的安装和使用

    https://blog.csdn.net/lamp_yang_3533/article/details/53136474   VMware Workstation 是一个虚拟PC的软件,利用VMwa ...

  5. PAT1113: Integer Set Partition

    1113. Integer Set Partition (25) 时间限制 150 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue ...

  6. Spring Boot全局支持CORS(跨源请求)的配置方法

    http://blog.csdn.net/zhangchao19890805/article/details/53893735

  7. Kali Linux配置ssh服务

    操作环境: 虚拟机操作系统: Kali Linux 2017.2 虚拟化软件: VMware Workstation 14 pro 虚拟机网络连接方式: 桥接模式 物理机操作系统: Windows10 ...

  8. mybatis整合spring获取配置文件信息出错

    描述:mybatis整合spring加载jdbc.properties文件,然后使用里面配置的值来 配置数据源,后来发现用户变成了admin- jdbc.properties的配置: 加载配置: 报错 ...

  9. java,maven工程打tar.gz包执行main方法

    一,需要在pom.xml文件添加plugin, 项目目录结构 <build> <plugins> <plugin> <artifactId>maven- ...

  10. 你不知道的JavaScript--Item15 prototype原型和原型链详解

    用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了, ...