什么是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. 【原】用Java编写第一个区块链(一)

    写这篇随笔主要是尝试帮助自己了解如何学习区块链技术开发. [本文禁止任何形式的全文粘贴式转载,本文来自 zacky31 的随笔] 目标: 创建一个最基本的"区块链" 实现一个简单的 ...

  2. 关于JSON字符串的处理与总结 【原创】

    这两天帮另一个实习生处理点前端的问题 遇到点JSON的处理 总结如下 ①一个JSON字符串    JSON.Parse(JSON字符串)—>JSON对象Object ②一个JSONArray   ...

  3. pyc

    当运行一个高级程序的时候,需要一个翻译机把高级语言变成计算机能读懂的机器语言的过程.这个过程分为两类: 编译 在程序执行之前,先通过编译器对程序执行一个编译的过程,把程序变成机器语言,运行时就不需要翻 ...

  4. Java 读书笔记 (十三) for each 循环

    JDK 1.5引进了一种新的循环类型,被称为foreach循环或者加强型循环,它能在不使用下标的情况下遍历数组. 实例: public class TestArray{ public static v ...

  5. 如何在苹果笔记本上装win7系统

    有一哥们,他说他boss给他配了台苹果,可是很不习惯,让我给装一个win系统.以下是我从百度借鉴的: 步骤一 先使用Boot Camp 分割磁盘   1 在Finder工具条中点选"前往&q ...

  6. java基础学习周计划之2--面向对象

    JAVA面向对象第一天一. 知识点:1. 类和对象二. 关键问题(理论):1. 简述什么是类.什么是对象2. 简述基本类型变量与引用类型变量赋值时的差别3. 简述null的含义三. 关键代码(操作): ...

  7. upload.go

    package api import (     "os"     "bytes"     "mime/multipart"     &qu ...

  8. BZOJ_1098_[POI2007]办公楼biu_链表优化BFS

    BZOJ_1098_[POI2007]办公楼biu_链表优化BFS Description FGD开办了一家电话公司.他雇用了N个职员,给了每个职员一部手机.每个职员的手机里都存储有一些同事的 电话号 ...

  9. BZOJ_3110_[Zjoi2013]K大数查询_整体二分+树状数组

    BZOJ_3110_[Zjoi2013]K大数查询_整体二分+树状数组 Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位 ...

  10. BZOJ_3261_最大异或和_可持久化trie

    BZOJ_3261_最大异或和_可持久化trie Description 给定一个非负整数序列{a},初始长度为N. 有M个操作,有以下两种操作类型: 1.Ax:添加操作,表示在序列末尾添加一个数x, ...