CAS目的:

在多线程中为了保持数据的准确性,避免多个线程同时操作某个变量,很多情况下利用关键字synchronized实现同步锁,使用synchronized关键字修可以使操作的线程排队等待运行,可以说是一种悲观策略,认为线程会修改数据,所以开始就把持有锁的线程锁住,其他线程只能是挂起状态,等待锁的释放,所以同步锁带来了问题:

主要的效率问题:在线程执行的时候,获得锁的线程在运行,其他被挂起的线程只能等待着持有锁的线程释放锁才有机会运行(现在JVM可能根据持有锁的时间来操作线程是否是被挂起还是自旋等待),在效率上都浪费在等待上。很可能这种锁是没有必要的,其他线程没有修改数据。在很多的线程切换的时候,由于有同步锁,就要涉及到锁的释放,加锁,这又是一个很大的时间开销。这里涉及到操作系统上的知识,关于线程之间的切换(被挂起和恢复)中间还要经历中断,时间片等等。

上面说了这么多,现在我们追求的是一种效率高,还要保证数据的安全的一种方法。

与锁(阻塞机制)的方式相比有一种更有效地方法,非阻塞机制,同步锁带来了线程执行时候之间的阻塞,而这种非阻塞机制在多个线程竞争同一个数据的时候不会发生阻塞的情况,这样在时间上就可以节省出很多的时间。

想到这里,知道volatile的可能会想到用volatile,使用volatile不会造成阻塞,volatile保证了线程之间的内存可见性和程序执行的有序性可以说已经很好的解决了上面的问题,但是一个很重要的问题就是,volatile不能保证原子性,对于复合操作,例如i++这样的程序包含三个原子操作:取指,增加,赋值。在《Java并发编程实战》这本书上有这样的一句话:变量的新值依赖于旧值时就不能使用volatile变量。实际上就是说的类似于i++这样的操作。具体详见:volatile关键字解析

什么是CAS:

现在采取的是CAS(Compare And Swap比较和交换)解决了volatile不能保证原子性。CAS通常比锁定要快得多,但这取决于争用的程度。因为如果读取和比较之间的值发生变化,CAS可能会强制重试,所以不能说某个方法就是绝对的好。CAS的主要问题是编程比锁定更困难。还好jdk提供了一些类用于完成基本的操作。

CAS主要包含三个操作数,内存位置V,进行比较的原值A,和新值B。当位置V的值与A相等时,CAS才会通过原子方式用新值B来更新V,否则不会进行任何操作。无论位置V的值是否等于A,都将返回V原有的值。通俗点说:我认为V地址的值应该是A,如果是,V地址的值更新为B,否则不修改并告诉V的值实际为多少(无论如何这个值都会通知到V)。上面说到了同步锁 是一种悲观策略,CAS是一种乐观策略,每次都开放自己,不用担心其他线程会修改变量等数据,如果其他线程修改了数据,那么CAS会检测到并利用算法重新计算。CAS也是同时允许一个线程修改变量,其他的线程试图修改都将失败,但是相比于同步锁,CAS对于失败的线程不会将他们挂起,他们下次仍可以参加竞争,这也就是非阻塞机制的特点。

下面用代码简单的实现CAS原理:

/**
* Created with IDEA
*
* @author DuzhenTong
* @Date 2018/2/1
* @Time 11:52
*/
public class SimpleCAS { private int value; public SimpleCAS(int value) {
this.value = value;
} public synchronized int get(){
return value;
} public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;//获取旧值
if(oldValue == expectedValue){//如果期望值与当前V位置的值相同就给予新值
value = newValue;
}
return oldValue;//返回V位置原有的值
} public synchronized boolean compareAndSet(int expectedValue, int newValue){
return (expectedValue == compareAndSwap(expectedValue, newValue));
} public static void main(String[] args) {
SimpleCAS simpleCAS = new SimpleCAS(3);
simpleCAS.compareAndSet(5, 10);
System.out.println(simpleCAS.get());//3 SimpleCAS simpleCAS1 = new SimpleCAS(1);
simpleCAS1.compareAndSet(1, 6);
System.out.println(simpleCAS1.get());//6
} }

从运行结果可以看出代码的原理:设置一个初始值(内存位置),期望值和新值进行比较,如果期望值和初始值一致,返回新值,否则返回初始值。意思是你在修改在一个变量A,假如它原来的值是3,所以你预期它是3,如果在你修改的时候,它被别的线程更新为5,那么就不符合你的预期,你的修改也不会生效

从Java5开始引入了底层的支持,在这之前需要开发人员编写相关的代码才可以实现CAS。在原子变量类Atomic***中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代码,在这里的代码都是调用了底层(核心代码调用native修饰的方法)的实现方法。在AtomicInteger源码中可以看getAndSet方法和compareAndSet方法之间的关系,compareAndSet方法调用了底层的实现,该方法可以实现与一个volatile变量的读取和写入相同的效果。在前面说到了volatile不支持例如i++这样的复合操作,在Atomic***中提供了实现该操作的方法。JVM对CAS的支持通过这些原子类(Atomic***)暴露出来,供我们使用。

CAS带来的问题:

ABA问题:CAS在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和某个值,A——>B——>A问题就变成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference类解决ABA问题,用Pair这个内部类实现,包含两个属性,分别代表版本号和引用,在compareAndSet中先对当前引用进行检查,再对版本号标志进行检查,只有全部相等才更新值。

时间问题:看起来CAS比锁的效率高,从阻塞机制变成了非阻塞机制,减少了线程之间等待的时间。每个方法不能绝对的比另一个好,在线程之间竞争程度大的时候,如果使用CAS,每次都有很多的线程在竞争,而锁可以避免这些状况,相反的情况,如果线程之间竞争程度小,使用CAS是一个很好的选择。

Java CAS机制详解的更多相关文章

  1. Java 反射机制详解(下)

    续:Java 反射机制详解(上) 三.怎么使用反射 想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属 ...

  2. Java 反射机制详解(上)

    一.什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...

  3. java异常处理机制详解

    java异常处理机制详解 程序很难做到完美,不免有各种各样的异常.比如程序本身有bug,比如程序打印时打印机没有纸了,比如内存不足.为了解决这些异常,我们需要知道异常发生的原因.对于一些常见的异常,我 ...

  4. Java SPI机制详解

    Java SPI机制详解 1.什么是SPI? SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.SPI是一种动态替换发现的机制, 比如有个 ...

  5. Java反射机制详解

    Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反 ...

  6. Java GC机制详解

    垃圾收集 Garbage Collection 通常被称为“GC”,本文详细讲述Java垃圾回收机制. 导读: 1.什么是GC 2.GC常用算法 3.垃圾收集器 4.finalize()方法详解 5. ...

  7. CAS机制详解

    目录 1. 定义 2. 实现原理 3. 无版本号CAS实战说明 4. CAS机制在Java中的应用 5. CAS的缺点 1. CPU开销过大 2. 不能保证代码块的原子性 3. ABA问题 6. JA ...

  8. Java CAS 原理详解

    1. 背景 在JDK 5之前Java语言是靠 synchronized 关键字保证同步的,这会导致有锁.锁机制存在以下问题: 在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问 ...

  9. java反射机制详解 及 Method.invoke解释

    JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为ja ...

随机推荐

  1. 云计算基础 (redhat7介绍及相关配置)

    redhat7简介 新版本的rhel7不再对32位架构的支持 引导程序: GRUB2,比之前的GRUB更强大,GRUB2支持bios,efi和openfiremware GRUB2支持mbr分区表和g ...

  2. R语言︱文本挖掘——词云wordcloud2包

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者看到微信公众号探数寻理中提到郎大为Chif ...

  3. 问题解决了,可是为什么呢?could not find the main class.program will exitmain

    今天重新学习socket编写简单的在线聊天,简单功能实现的情况下,一时心血来潮便想要把这程序打成可执行的jar包,以便于在桌面直接双击运行. 参照自己之前写的那篇<>打好两个jar包以后却 ...

  4. 使用VLC和live555MediaServer搭建RTSP服务器

    一.使用VLC搭建 服务端 1.打开"媒体--流"选项,如图所示:  2.点击"添加"按钮选择流媒体文件,然后点击"串流"按钮  3.点击& ...

  5. Java中的throw和throws的区别

    Java中的throw和throws的区别 1.throw关键字用于方法体内部,而throws关键字用于方法体部的方法声明部分: 2.throw用来抛出一个Throwable类型的异常,而throws ...

  6. FAT32文件系统的存储组织结构(二)

    前面已经基于一个格式化的空U盘分析了一下FAT32文件系统存储的组织结构,下面我们从文件操作的角度来分析一下文件系统的运作机制.由于换了个U盘,所以仍然贴出刚格式化的空U盘的几个重要的数据区如下:   ...

  7. Android的sdk、api及工程目录说明

    SDK下包的说明1:add-ons:Android开发需要的第三方文件和软件库2:docs:Android的文档.包括开发指南.API参考.资源等3:extras:扩展的附加包4:platforms: ...

  8. Monkeyscript---获取包名主界面名和位置坐标

    一.monkey随机事件有三类:点击.输入.手势 二.Monkeyscript难点:如何获取坐标 三.Monkeyscript API: 1.轨迹球(手势)事件: DispatchTrackball( ...

  9. 21个DOS常用命令

    DOS,可别不知道DOS在哪里运行,有好几种方法1.开始菜单---程序---附件---命令提示符2.开始菜单---运行---打命令CMD 一.常用DOS命令(1)cd..                ...

  10. 你还在为如何区分ASCII编码、GB2312编码、Unicod、UTF-8编码而烦恼吗,一篇文章让你柳暗花明

    字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特 ...