一、CAS(无锁的执行者)

  CAS包含3个参数:内存值  V  旧的预期值  A  新值  B

  当且仅当V值等于A值时,将V的值改为B值,如果V值和A值不同,说明已经有其他线程做了更新,则当前线程什么都不做,最后返回当前V的真实值。CAS操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功地完成操作。

  当多个线程同时使用CAS同时操作同一个变量时,只有其中一个线程会胜出并成功更新,其余均会失败;但失败的线程并不会挂起,仅是被告知失败,并且允许再次尝试,也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作的影响,并执行相应的处理措施。CAS的关键点在于,系统在硬件层面保证了比较并交换操作的原子性,处理器使用基于对缓存加锁或总线枷锁的方式来实现多处理器之间的原子操作。

  由于是无锁操作,因此不可能出现死锁情况。CAS是非阻塞算法的一种常见实现。CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

  一个线程间共享的变量,首先在主存中会保留一份,然后每个线程的工作内存也会保留一份副本。这里说的预期值,就是线程保留的副本。当该线程从主存中获取该变量的值后,主存中该变量可能已经被其他线程刷新了,但是该线程工作内存中该变量却还是原来的值,这就是所谓的预期值了。当你要用CAS刷新该值的时候,如果发现线程工作内存和主存中不一致了,就会失败,如果一致,就可以更新成功。

CAS的优缺点

  CAS由于是在硬件层面保证的原子性,不会锁住当前线程,它的效率是很高的。

  CAS虽然很高效地实现了原子操作,但它依然存在三个问题。如下:

ABA问题

  CAS会导致“ABA问题”。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差中可能导致数据发生变化。

  比如一个线程one从内存位置V中取出A,这是另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

  部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号, 一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

循环时间长开销大

  自选CAS如果长时间不成功,会给CPU带来非常大的执行开销。因此CAS不适合竞争十分频繁的场景。

只能保证一个共享变量的原子操作

  当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。

这里粘贴一个,模拟CAS实现的计数器:

public class CASCount implements Runnable{
private SimilatedCAS counter = new SimilatedCAS(); public void run(){
for(int i=0;i<10000;i++){
System.out.println(this.increment());
}
} public int increment(){
int oldValue = counter.getValue();
int newValue = oldValue + 1; while(!counter.compareAndSwap(oldValue,newValue)){
//如果CAS失败,就去拿新值继续执行CAS
oldValue = counter.getValue();
newValue = oldValue + 1;
}
return newValue;
} public static void main(String[] args){
Runnable run = new CASCount(); new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
} class SimilatedCAS{
private int value; public int getValue(){
return value;
} //这里只能用synchronized了,毕竟无法调用操作系统的CAS
public synchronized boolean compareAndSwap(int expectedValue,int newValue){
if(value == expectedValue){
value = newValue;
return true;
}
return false;
}
}

二、原子包  java.util.concurrent.atomic

  JDK1.5的原子包:java.util.concurrent.atomic这个包里面提供了一组原子类。其基本特性是:在多线程环境下,当有多个线程同时执行这些类实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择另一个线程进入。相对于synchronized这种阻塞算法,Atomic类在软件层面上是非阻塞的,它的原子性其实是在硬件层面上借助相关的指令来保证的,由于一般CPU切换时间比CPU指令集操作更加长,所以JUC在性能上有了很大的提升。

  AtomicInteger是一个支持原子操作的Integer类,就是保证对AtomicInteger类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致的问题。如果不使用AtomicInteger,要实现一个按顺序获取的ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的ID的现象。Java并发库中的AtomicXXX类均是基于这个原语的实现,拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的:

  来看看++i是怎么做到的。如下代码:

 public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) { //CAS 自旋,一直尝试,直达成功
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
  return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}

  在这里getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作,非阻塞算法。

  其中,unsafe.compareAndSwapInt()是一个native方法,正是调用CAS原语完成该操作。

首先假设有一个变量i,i的初始值为0。每个线程都对i进行+1操作。CAS是这样保持同步的:

  假设有两个线程,线程1读取内存中的值为0,current=0,next=1,然后挂起,然后线程2对i进行操作,将i的值变成了1。线程2执行完,回到线程1,进入if里的compareAndSet方法,该方法进行的操作的逻辑是,(1)如果操作数的值在内存中没有被修改,返回true,然后compareAndSet方法返回next的值;(2)如果操作数的值在内存中被修改了,则返回false,重新进入下一次循环,重新得到current的值为1,next的值为2,然后再比较,由于这次没有被修改,所以直接返回2。

  那么为什么自增操作要通过CAS来完成?仔细观察getAndIncrement()方法,发现自增操作其实拆成了两步完成的:

    int current = get();

    int next = current +1;

  由于volatile只能保证读取或写入的是最新值,那么可能出现以下情况:

    1)A线程执行get()操作,获取current值(假设为1)

    2)B线程执行get()操作,获取current值(为1)

    3)B线程执行next = current +1操作,next=2

    4)A线程执行next = current +1操作,next=2

  这样的结果明显不是我们想要的,所以,自增操作必须采用CAS来完成。

  

CAS(比较并交换)的更多相关文章

  1. CAS 比较并交换

    简介 CAS 的全称为 Compare-And-Swap,他是一条 CPU 并发源语. 他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的. CAS 并发原语体现在 J ...

  2. 原子类型的使用&Unsafe&CAS

    在项目中也经常可以见到原子类型(AtomicXXX)的使用,而且AtomicXXX常用来代替基本类型或者基本类型的包装类型,因为其可以在不加同步锁的情况下保证线程安全(只对于原子操作). 下面以Ato ...

  3. DLC双端锁,CAS,ABA问题

    一.什么是DLC双端锁?有什么用处? 为了解决在多线程模式下,高并发的环境中,唯一确保单例模式只能生成一个实例 多线程环境中,单例模式会因为指令重排和线程竞争的原因会出现多个对象 public cla ...

  4. CAS 分析

    CAS是什么 (1) CAS(Compare and Swap) 比较并交换, 比较并交换是在多线程并发时用到的一种技术 (2) CAS是原子操作, 保证并发安全性, 而不是保证并发同步. (3) C ...

  5. CAS原理解析

    CAS底层原理 概念 CAS的全称是Compare-And-Swap,它是CPU并发原语 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的 CAS并发原语体现在Jav ...

  6. 基础篇:详解锁原理,volatile+cas、synchronized的底层实现

    目录 1 锁的分类 2 synchronized底层原理 3 Object的wait和notify方法原理 4 jvm对synchronized的优化 5 CAS的底层原理 6 CAS同步操作的问题 ...

  7. ReentrantLock锁-CAS与阻塞

    ReentrantLock锁 ReentrantLock通过原子操作和阻塞实现锁原理,一般使用lock获取锁,unlock释放锁 lock的时候可能被其他线程获得所,那么此线程会阻塞自己,关键原理底层 ...

  8. CAS你知道吗?底层如何实现?ABA问题又是什么?关于这些你知道答案吗

    CAS你知道吗?如何实现? 1. compareAndSet 在volatile当中我们提到,volatile不能保证原子语义,所以当用到变量自增时,如果用到synchronized会太"重 ...

  9. ConcurrentLinkedQueue代码解析

    原因:学习ConcurrentLinkedQueue是看到akka框架的默认邮箱是使用ConcurrentLinkedQueue实现的. 1. ConcurrentLinkedQueue在java.u ...

  10. java并发编程(8)原子变量和非阻塞的同步机制

    原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用 ...

随机推荐

  1. windowsServer---- 在iis 上安装网站

    1.找到信息服务IIS 管理器如图: 2.进入后进行配置 3.添加本地网站 配置网站 如果域名没有解析的话,可以在添加一个  端口用于测试 点击浏览就行查看 如果报错 解决:找到目录浏览,并启动 点击 ...

  2. eclipse_neon 的Spket 目录下只有一个Task Tags,没有其他的选项,导致没有办法添加提示文件! 添加sdk文件之后还是没有办法显示的解决办法

    问题解决办法: 将 spket-1.6.23的安装包里面的features  plugins 单独复制到D:\eclipse_neon\dropins 目录下,重启一下eclipse即可正常显示! 添 ...

  3. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  4. PAT 1012 The Best Rank 排序

    To evaluate the performance of our first year CS majored students, we consider their grades of three ...

  5. WPF TreeView 虚拟化-设置滚动到选中项

    前言 列表滚动到具体的数据项? ListBox提供了简易快捷的滚动定位函数ScrollIntoView. TreeView树状结构列表,则没有此类方法,无法与ListBox一样,直接设置滚动到具体的数 ...

  6. Razor_01 第一个应用程序

    自己开始从头深造 自己看了一下,开头真的不适合初学者,没有重点,对不起各位了 . 但你可以在5 分钟以后看,对于初学者还时有深大的用处的 链 接: https://pan.baidu.com/s/1V ...

  7. python中list常用的方法

    Python 列表    list    (以下内容为比较初级适合小白查看的笔记)   一.介绍: 列表是Python中内置有序.可变序列,列表的所有元素放在一对中括号“[]”中,并使用逗号分隔开: ...

  8. Java并发专题

    ——参考于码农求职小助手公众号 1.并行和并发有什么区别? 1. 并行是指两个或者多个事件在同一时刻发生:而并发是指两个或多个事件在同一时间间隔发生: 2. 并行是在不同实体上的多个事件,并发是在同一 ...

  9. openstack 搭建

    #所有节点修改ip,主机名和hosts解析 controller 10.0.0.11 controller compute1 10.0.0.31 compute1 #所有节点准备本地repo源 rm ...

  10. Python比较配置文件

    工作中最常见的配置文件有四种:普通key=value的配置文件.Json格式的配置文件.HTML格式的配置文件以及YAML配置文件. 这其中以第一种居多,后三种在成熟的开源产品中较为常见,本文只针对第 ...