CAS机制与自旋锁

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

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

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

1 1 public class CASDemo { 2 2 3 3 public static void main(String[] args) { 4 4 5 5 //初始值5 6 6 AtomicInteger atomicInteger = new AtomicInteger(5); 7 7 8 8 //和5比较,设置为10 9 9 System.out.println("预期值:5,当前值:"+atomicInteger); 1010 System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 10)); 1111 //和5比较,设置为15 1212 System.out.println("预期值:5,当前值:"+atomicInteger); 1313 System.out.println("是否设置成功:"+atomicInteger.compareAndSet(5, 15)); 1414 1515 System.out.println("当前值:"+atomicInteger); 1616 } 1717 }

输出为:

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

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

1//va1为对象,var2为地址值,var4是要增加的值,var5为当前地址中最新的值public final int getAndAddInt(Object var1, long var2, int var4) { 2 int var5; 3 do { 4 var5 = this.getIntVolatile(var1, var2); 5 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 6 7 return var5; 8 }

首先通过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>,或者叫时间戳的原子引用,类似于乐观锁。

1 0 // ABA问题及解决方式 1 public class ABADemo { 2 2 3 3 private static AtomicReference<String> atomicReference = new AtomicReference<>("A"); 4 4 private static AtomicStampedReference<String> stampReference = new AtomicStampedReference<>("A",1); 5 5 6 6 public static void main(String[] args){ 7 7 new Thread(()->{ 8 8 //获取到版本号 9 9 int stamp = stampReference.getStamp(); 1010 System.out.println("t1获取到的版本号:"+stamp); 1111 try { 1212 //暂停1秒,确保t1,t2版本号相同 1313 TimeUnit.SECONDS.sleep(1); 1414 } catch (InterruptedException e) { 1515 e.printStackTrace(); 1616 } 1717 atomicReference.compareAndSet("A","B"); 1818 atomicReference.compareAndSet("B","A"); 1919 2020 stampReference.compareAndSet("A","B",stamp,stamp+1); 2121 stampReference.compareAndSet("B","A",stamp+1,stamp+2); 2222 System.out.println("t1线程ABA之后的版本号:"+stampReference.getStamp()); 2323 2424 },"t1").start(); 2525 2626 new Thread(()->{ 2727 //获取到版本号 2828 int stamp = stampReference.getStamp(); 2929 System.out.println("t2获取到的版本号:"+stamp); 3030 try { 3131 //暂停2秒,等待t1执行完成ABA 3232 TimeUnit.SECONDS.sleep(2); 3333 } catch (InterruptedException e) { 3434 e.printStackTrace(); 3535 } 3636 System.out.print("普通原子类无法解决ABA问题: "); 3737 System.out.println(atomicReference.compareAndSet("A","C")+"\t"+atomicReference.get()); 3838 System.out.print("版本号的原子类解决ABA问题: "); 3939 System.out.println(stampReference.compareAndSet("A","C",stamp,stamp+1)+"\t"+stampReference.getReference()); 4040 4141 },"t2").start(); 4242 } 4343 }

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

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

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

CAS机制与自旋锁 - HelloWorld