CAS(Compare-and-Swap),即比较并替换,java并发包中许多Atomic的类的底层原理都是CAS。

它的功能是判断内存中某个地址的值是否为预期值,如果是就改变成新值,整个过程具有原子性。

具体体现于sun.misc.Unsafe类中的native方法,调用这些native方法,JVM会帮我们实现汇编指令,这些指令是CPU的原子指令,因此具有原子性。

 public class CASDemo {

     public static void main(String[] args) {

         //初始值5
AtomicInteger atomicInteger = new AtomicInteger(5); //和5比较,设置为10
System.out.println("预期值:5,当前值:"+atomicInteger);
System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 10));
//和5比较,设置为15
System.out.println("预期值:5,当前值:"+atomicInteger);
System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 15)); System.out.println("当前值:"+atomicInteger);
}
}

输出为:

预期值:5,当前值:5
是否设置成功:true
预期值:5,当前值:10
是否设置成功:false
当前值:10

下面看一下getAndAddInt在底层Unsafe类中的代码(自旋锁),运用到了CAS

//va1为对象,var2为地址值,var4是要增加的值,var5为当前地址中最新的值
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;
}

首先通过volatile的可见性,取出当前地址中的值,作为期望值。如果期望值与实际值不符,就一直循环获取期望值,直到set成功。

适用场景:

  1. CAS 适合简单对象的操作,比如布尔值、整型值等;

  2. CAS 适合冲突较少的情况,如果太多线程在同时自旋,那么长时间循环会导致 CPU 开销很大;

CAS的缺点:

  1. CPU开销过大 : 在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。

  2. 不能保证代码块的原子性:CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

  3.  ABA问题:如果内存地址V初次读取的值是A,在CAS等待期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。

ABA问题以及解决:使用带版本号的原子引用AtomicStampedRefence<V>,或者叫时间戳的原子引用,类似于乐观锁。

 0 // ABA问题及解决方式
1 public class ABADemo { private static AtomicReference<String> atomicReference = new AtomicReference<>("A");
private static AtomicStampedReference<String> stampReference = new AtomicStampedReference<>("A",1); public static void main(String[] args){
new Thread(()->{
//获取到版本号
int stamp = stampReference.getStamp();
System.out.println("t1获取到的版本号:"+stamp);
try {
//暂停1秒,确保t1,t2版本号相同
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet("A","B");
atomicReference.compareAndSet("B","A"); stampReference.compareAndSet("A","B",stamp,stamp+1);
stampReference.compareAndSet("B","A",stamp+1,stamp+2);
System.out.println("t1线程ABA之后的版本号:"+stampReference.getStamp()); },"t1").start(); new Thread(()->{
//获取到版本号
int stamp = stampReference.getStamp();
System.out.println("t2获取到的版本号:"+stamp);
try {
//暂停2秒,等待t1执行完成ABA
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("普通原子类无法解决ABA问题: ");
System.out.println(atomicReference.compareAndSet("A","C")+"\t"+atomicReference.get());
System.out.print("版本号的原子类解决ABA问题: ");
System.out.println(stampReference.compareAndSet("A","C",stamp,stamp+1)+"\t"+stampReference.getReference()); },"t2").start();
}
}

输出结果:普通原子引用类在另一个线程完成ABA之后继续修改(把A改成了C),带版本号原子引用有效的解决了这个问题。

t1获取到的版本号:1
t2获取到的版本号:1
t1线程ABA之后的版本号:3
普通原子类无法解决ABA问题: true C
版本号的原子类解决ABA问题: false A

CAS机制与自旋锁的更多相关文章

  1. 二、多线程基础-乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁

    1.10乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 ...

  2. 我们常说的 CAS 自旋锁是什么

    CAS(Compare and swap),即比较并交换,也是实现我们平时所说的自旋锁或乐观锁的核心操作. 它的实现很简单,就是用一个预期的值和内存值进行比较,如果两个值相等,就用预期的值替换内存值, ...

  3. 多线程之美6一CAS与自旋锁

    1.什么是CAS CAS 即 compare and swap 比较并交换, 涉及到三个参数,内存值V, 预期值A, 要更新为的值B, 拿着预期值A与内存值V比较,相等则符合预期,将内存值V更新为B, ...

  4. 线程安全之CAS机制详解(分析详细,通俗易懂)

    背景介绍:假设现在有一个线程共享的变量c=0,让两个线程分别对c进行c++操作100次,那么我们最后得到的结果是200吗? 1.在线程不安全的方式下:结果可能小于200,比如当前线程A取得c的值为3, ...

  5. 【C# 线程】并发编程的基石——CAS机制

    其实Java并发框架的基石一共有两块,一块是本文介绍的CAS,另一块就是AQS,后续也会写博客介绍. 什么是CAS机制 CAS机制是一种数据更新的方式.在具体讲什么是CAS机制之前,我们先来聊下在多线 ...

  6. linux自旋锁、互斥锁、信号量

    为了避免并发,防止竞争.内核提供了一组同步方法来提供对共享数据的保护. 我们的重点不是介绍这些方法的详细用法,而是强调为什么使用这些方法和它们之间的差别. Linux 使用的同步机制可以说从2.0到2 ...

  7. Linux内核同步:自旋锁

    linux内核--自旋锁的理解 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”.如果配置了CON ...

  8. 菜鸟nginx源代码剖析数据结构篇(十) 自旋锁ngx_spinlock

    菜鸟nginx源代码剖析数据结构篇(十) 自旋锁ngx_spinlock Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.cs ...

  9. 使用C++11原子量实现自旋锁

    一.自旋锁 自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问.与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁.当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处 ...

随机推荐

  1. JavaScript prototype详解

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

  2. 关于 行高lineheight的概念及与Vertical-align、内容框、基线等的关系

    1.关于行高 行高:顾名思意指一行文字的高度.具体来说是指两行文字间基线之间的距离,他也是底线和顶线之间距离   (1)内容区

  3. Python startswith()方法

    描述 Python startswith() 方法用于检查字符串是否是以指定子字符串开头,如果是则返回 True,否则返回 False.如果参数 beg 和 end 指定值,则在指定范围内检查. 语法 ...

  4. Python Web 框架:Tornado

    1.Tornado Tornado:python编写的web服务器兼web应用框架 1.1.Tornado的优势 轻量级web框架 异步非阻塞IO处理方式 出色的抗负载能力 优异的处理性能,不依赖多进 ...

  5. Python logger /logging

    # !/user/bin/python # -*- coding: utf-8 -*- ''' subprocess : 需要在linux平台上测试 shell logging ''' import ...

  6. json & pickle 序列化

    #!/usr/bin/python # -*- coding: utf-8 -*- # 序列化: 例如把字典写进文件 info = { 'name': 'alex', 'age': 22 } f = ...

  7. bzoj 3166 可持久化Tire

    每一个数能做出的贡献就是其两端第二个比他大的中间的数和他的异或值 按权值大小排序,按照位置扔进set,set内的元素都是比他大的,也是全的 然后Tire上跑就行了.. #include<cstd ...

  8. BZOJ3252: 攻略 可并堆

    网上有很多人说用dfs序+线段树做...其实stl的堆可以...可并堆可以...很多奇奇怪怪的东西都能做... 可并堆比较好想...也比较好写... 分析: 首先,这是一个网络流做不了的题...数据太 ...

  9. java Dated Dateformat Calendar

    Date类概述 类Date表示特定的瞬间,精确到毫秒.1000毫秒=1秒 时间的原点:公元1970年 一月一日,午夜0:00:00 对应的毫秒值就是0 注意:时间和日期的计算,必须依赖毫秒值 long ...

  10. Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)

    ========================================================          ================================== ...