CAS(CompareAndSweep)工作方式

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。   

​ CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。

过程图解如下:

  1. 内存地址V当中,存储着值为10的变量

  1. 此时线程1想要把变量的值增加1,对线程1来说,旧的预期值A=10,要修改的新值B=11

  1. 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

  1. 线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

  1. 线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

  1. 这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

  1. 线程1进行SWAP,把地址V的值替换为B,也就是12。


CAS的特点

结合CAS和volatile可以实现无锁并发,适用于线程数少,多核CPU场景下

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我再重试呗
  • synchronized是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会
  • CAS体现的是无锁并发,无阻塞并发
    • 因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
    • 但如果竞争激烈,重试必然会频繁发生,反而效率会受影响

原子整数

JUC并发包提供了

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

以AtomicInteger为例

public static void main(String[] args){

        AtomicInteger i = new AtomicInteger(3);

        System.out.println(i.incrementAndGet()); // ++i

        System.out.println(i.getAndIncrement()); // i++

        System.out.println(i.addAndGet(12)); //先加后取值

        System.out.println(i.getAndAdd(12)); //先取值后加

        System.out.println(i.get());

        //                              读取到    设置值
System.out.println(i.updateAndGet(x -> x * 10)); }

原子引用ABA问题

public class getClassTest {
static AtomicReference<String> ref = new AtomicReference<>("A"); public static void main(String[] args) { System.out.println("main start...");
String prev = ref.get(); other(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": A -> C " + ref.compareAndSet("A", "C"));
} public static void other() { new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": A -> B " + ref.compareAndSet("A", "B"));
}, "t1").start(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": B -> A " + ref.compareAndSet("B", "A"));
}, "t2").start();
}
}

主线程仅能判断出共享变量的值与最初值A是否相同,不能感知到这种从A改为B又改回A的情况,如果主线程希望:

只要有其他线程 动过了 共享变量,那么自己的cas就算失败,这时,仅比较值是不够的,需要再加一个版本号

public class getClassTest {
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0); public static void main(String[] args) { System.out.println("main start...");
//获取值A
String prev = ref.getReference();
//获取版本号
int stamp = ref.getStamp(); System.out.println(Thread.currentThread().getName() + " stamp : " + stamp); other(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() +" stamp : "+ stamp); System.out.println(Thread.currentThread().getName() + ": A -> C " + ref.compareAndSet(prev, "C", stamp, stamp + 1));
} public static void other() { new Thread(() -> {
int stamp = ref.getStamp(); System.out.println(Thread.currentThread().getName() + " stamp : " + stamp); System.out.println(Thread.currentThread().getName() + ": A -> B " + ref.compareAndSet(ref.getReference(), "B", stamp, stamp + 1));
}, "t1").start(); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} new Thread(() -> {
int stamp = ref.getStamp(); System.out.println(Thread.currentThread().getName() + " stamp : " + stamp); System.out.println(Thread.currentThread().getName() + ": B -> A " + ref.compareAndSet(ref.getReference(), "A", stamp, stamp + 1));
}, "t2").start();
}
}

CAS的理解的更多相关文章

  1. .NET:通过 CAS 来理解数据库乐观并发控制,顺便给出无锁的 RingBuffer。

    背景 大多数企业开发人员都理解数据库乐观并发控制,不过很少有人听说过 CAS(我去年才听说这个概念),CAS 是多线程乐观并发控制策略的一种,一些无锁的支持并发的数据结构都会使用到 CAS,本文对比 ...

  2. (转)乐观的并发策略——基于CAS的自旋

    悲观者与乐观者的做事方式完全不一样,悲观者的人生观是一件事情我必须要百分之百完全控制才会去做,否则就认为这件事情一定会出问题:而乐观者的人生观则相反,凡事不管最终结果如何,他都会先尝试去做,大不了最后 ...

  3. 乐观锁--CAS

    悲观锁与乐观锁的区别 悲观锁会把整个对象加锁占为已有后才去做操作,Java中的Synchronized属于悲观锁.悲观锁有一个明显的缺点就是:它不管数据存不存在竞争都加锁,随着并发量增加,且如果锁的时 ...

  4. 乐观的并发策略——基于CAS的自旋

    悲观者与乐观者的做事方式完全不一样,悲观者的人生观是一件事情我必须要百分之百完全控制才会去做,否则就认为这件事情一定会出问题:而乐观者的人生观则相反,凡事不管最终结果如何,他都会先尝试去做,大不了最后 ...

  5. Java多线程系列——原子类的实现(CAS算法)

    1.什么是CAS? CAS:Compare and Swap,即比较再交换. jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronou ...

  6. 沉淀再出发:java中的CAS和ABA问题整理

    沉淀再出发:java中的CAS和ABA问题整理 一.前言 在多并发程序设计之中,我们不得不面对并发.互斥.竞争.死锁.资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切 ...

  7. CAS无锁机制原理

    原子类 java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读 ...

  8. 深入浅出CAS

    后端开发中大家肯定遇到过实现一个线程安全的计数器这种需求,根据经验你应该知道我们要在多线程中实现 共享变量 的原子性和可见性问题,于是锁成为一个不可避免的话题,今天我们讨论的是与之对应的无锁 CAS. ...

  9. CAS无锁模式

    一.java内存模型:JMM 在内存模型当中定义一个主内存,所有声明的实例变量都存在于主内存当中,主内存的数据会共享给所有线程,每一个线程有一个块工作内存,工作内存当中主内存数据的副本当更新数据时,会 ...

随机推荐

  1. Typora常用编辑方法-一个能将写博客变作享受的工具

    1,标题 ctrl+数字(1~5) 2,序号 数字序号 数字 + . +空格,之后回车换行会自动产生数字序号 非数字序号 有三种 实心圆 ,非实心圆与实心方框 都是 +空格 ,之后按tab键向内缩进, ...

  2. Python脚本打包成exe执行文件

    需求 一个教辅目录结构检查工具,目录结构是[书籍]-[章节]-[题目|答案]-[*.jpg],后台有个异步处理的服务,需要强依赖这个目录结构. 书籍解析是单独的pipeline,日志对用户不可见,这里 ...

  3. iovec结构体使用说明

    http://blog.163.com/lichuan0502@126/blog/static/9933534820111033228285/

  4. 一键部署etcd集群管理脚本

    一.编写脚本 1 #!/bin/sh 2 # 安装 3 # ./run.sh etcd03 etcd01=http://192.168.2.44:2380,etcd02=http://192.168. ...

  5. java面试-生产环境服务器变慢,谈谈你的诊断思路

    1.uptime:查询linux系统负载 11:16:16 系统当前时间 up 64 days, 19:23 从上次启动开始系统运行的时间3 users 连接数量,同一用户多个连接的时候算多个load ...

  6. 拇指记者深入Android公司,打探事件分发机制背后的秘密

    前言 聊到事件分发,很多朋友就会想到view的dispatchTouchEvent,其实在此之前,Android还做了很多工作. 比如跨进程获取输入事件的方式?在dispatchTouchEvent责 ...

  7. 201871030116-李小龙 实验一 软件工程准备——Blog

    项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/2018CST 这个作业要求链接 https://www.cnblogs.com/nwnu-dai ...

  8. 201871030125-王芬 实验二 个人项目-《D{0-1}问题》软件项目报告

    实验二 个人项目-<D{0-1}问题>软件项目报告 项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/2018CST 这个作业要求链接 ...

  9. (十三)struts2的输入校验

    输入校验是web应用必须处理的问题,要防止用户的误输入和恶意非法输入.struts2给我们提供了非常强大的输入校验体系. 输入校验分为客户端校验和服务器端校验.一般开发中两者都用,但是服务端校验必须使 ...

  10. ES系列(二):基于多播的集群发现实现原理解析

    ES作用超强悍的搜索引擎,除了需要具有齐全的功能支持,超高的性能,还必须要有任意扩展的能力.一定程度上,它是一个大数据产品.而要做扩展性,集群自然少不了.然而单独的集群又是不够的,能够做的事情太少,所 ...