JUC并发编程之CAS操作及底层实现
2022/1/24 20:06:22
本文主要是介绍JUC并发编程之CAS操作及底层实现,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
概念
- CAS全称是Compare And Swap,意为比较并交换,是CPU的原子操作
- CAS是一种抽象思想,并不是一种具体实现.
- CAS操作有三个参数,待修改值的内存地址,期望值,新值
- 主要思想是判断内存中某个位置的值是否等于期望值,如果等于则使用新值交换旧值,不等于则修改失败
- CAS非阻塞同步的实现了线程安全
- CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。
CAS 问题
CAS 方式为乐观锁,synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。
但使用 CAS 方式也会有几个问题:
ABA问题
因为CAS需要在操作值的时候,检查值有没有发生变化,比如没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时则会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。
从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
ABA问题的解决
ABA问题可以通过对数据添加版本号的方式进行解决
AtomicStampedReference解决ABA问题
AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。
循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ij。
从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
CAS在Java中的实现
java中为我们提供了AtomicXXX 原子类(底层基于CAS进行更新数据的),不需要加锁就在多线程并发场景下实现数据的一致性。
public class CASDemo{ public static void main(string[] args){ AtomicInteger atomicInteger = new AtomicInteger(5);// mian do thing. . . . .. System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current data: "+atomicInteger.get()); System.out.println(atomicInteger.compareAndset(5, 1024)+"\t current data: "+atomicInteger.get()); } }
true 2019 false 2019
原子类底层原理 atomicInteger为例
atomicInteger.getAndIncrement()源码(此方法实现原子的i++操作)
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); //当前对象,内存地址偏移量,操作数 }
看出此方法是调用了unsafe类中的方法
Unsafe类
由于Java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。
Unsafe类功能
Unsafe与CAS
Unsafe类中关于CAS的方法
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) { int i; do i = getIntVolatile(paramObject, paramLong); while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt)); return i; } public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2) { long l; do l = getLongVolatile(paramObject, paramLong1); while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2)); return l; } public final int getAndSetInt(Object paramObject, long paramLong, int paramInt) { int i; do i = getIntVolatile(paramObject, paramLong); while (!compareAndSwapInt(paramObject, paramLong, i, paramInt)); return i; } public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2) { long l; do l = getLongVolatile(paramObject, paramLong1); while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2)); return l; } public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2) { Object localObject; do localObject = getObjectVolatile(paramObject1, paramLong); while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2)); return localObject; }
从源码中发现,内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)。
又从Unsafe类中发现,原子操作其实只支持下面三个方法。
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3); public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2); public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
全部原子类
原子更新基本类型
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
- AtomicBoolean: 原子更新布尔类型。
- AtomicInteger: 原子更新整型。
- AtomicLong: 原子更新长整型。
原子更新数组
通过原子的方式更新数组里的某个元素,Atomic包提供了以下的4个类:
- AtomicIntegerArray: 原子更新整型数组里的元素。
- AtomicLongArray: 原子更新长整型数组里的元素。
- AtomicReferenceArray: 原子更新引用类型数组里的元素。 这三个类的最常用的方法是如下两个方法:
- get(int index):获取索引为index的元素值。
- compareAndSet(int i,E expect,E update): 如果当前值等于预期值,则以原子方式将数组位置i的元素设置为update值
原子更新引用类型
Atomic包提供了以下三个类:
- AtomicReference: 原子更新引用类型。
- AtomicStampedReference: 原子更新引用类型, 内部使用Pair来存储元素值及其版本号。
- AtomicMarkableReferce: 原子更新带有标记位的引用类型。
原子更新字段类
Atomic包提供了四个类进行原子字段更新:
- AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
- AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
- AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
- AtomicReferenceFieldUpdater: 上面已经说过此处不在赘述。
这篇关于JUC并发编程之CAS操作及底层实现的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享