博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CAS(Compare And Swap)
阅读量:4984 次
发布时间:2019-06-12

本文共 2993 字,大约阅读时间需要 9 分钟。

之前的文章讲了和都是通过锁来保证线程安全的,锁机制存在一些问题,例如:

  ❤ 在多线程的竞争下,加锁、释放锁会导致很多线程的上下文切换和调度,对性能有一定的影响;

  ❤ 一个线程持有锁会导致其他需要此锁的线程挂起(强行在锁的区域将并行变为串行);

  ❤ 使用不当还会导致死锁、饥饿、活锁等;

也许你会说,也可以用,volatile是轻量级的锁,但是不能保证原子性,所以最后还是会回到锁的机制上来;

synchronized和ReentrantLock都是独占锁,独占锁是一种悲观锁,会导致其余需要锁的线程挂起,等待持有锁的线程释放锁的资源,并且每次只能有一个线程执行;而另一个更加有效的锁就是乐观锁,乐观锁就是乐观的认为每次操作都没有冲突,如果有,则重试,直到成功为止,乐观锁使用到的机制就是CAS。

CAS(Compare And Swap)

CAS:Compare and Swap 即:比较并交换。设计并发计算时常用到的一种技术,java.util.concurrent包的基础建立在CAS之上,没有CAS就没有此包,可见CAS的重要性。

在Java语言出现之前,并发就已经广泛存在并在服务器领域得到了广泛的应用。所以硬件厂商很早就在芯片中加入了大量支持并发操作的原语,从而使硬件性能得到提升,在Intel中,采用的是cmpxchg指令。

在Java的发展初期,Java语言是不能利用硬件提供的这些便利来提升系统的性能的,随着Java的发展,JNI(Java Native Interface)的出现,使得Java程序可以越过JVM直接调用本地(本机)上的一些方法,这样不仅使得Java在并发上的手段增多了,同时也提高了Java系统的性能。

CAS有三个操作数:内存值V,旧的预期值A、要修改的新值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。

CAS是通过Unsafe类来实现的,下面看看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);

 上面这三种方法都是Native本地方法,而且都是原子操作(硬件保证)。第一个参数表示需要更新的对象,第二个long表示的是该对象在内存中的偏移地址,第三个是预期值(即旧值),第四个是新值。

下面我们通过AtomicInteger来增加对CAS的理解,先来看AtomicInteger的类的变量定义,源码如下:

1 private static final Unsafe unsafe = Unsafe.getUnsafe(); 2     private static final long valueOffset; 3  4     static { 5       try { 6         valueOffset = unsafe.objectFieldOffset 7             (AtomicInteger.class.getDeclaredField("value")); 8       } catch (Exception ex) { throw new Error(ex); } 9     }10 11     private volatile int value;

上述代码解释:

  ❤ Unsafe是CAS的核心。

  ❤ valueOffset是变量在内存中的偏移地址,因为Unsafe就是根据偏移地址去获取数据的原值的;

  ❤ value是volatile关键字修饰的,这个非常重要,这保证了变量在线程中的可见性;

再来看一个方法incrementAndGet(),源码如下:

public final int incrementAndGet() {        for (;;) {            int current = get();            int next = current + 1;            if (compareAndSet(current, next))                return next;        }    }public final boolean compareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }

incrementAndGet()该方法每次增加1,并返回增加后的值。相当于++i的功能,但是++i不是原子性的,incrementAndGet()是原子性的。

从上述代码可以看出,incrementAndGet()方法内是一个死循环,保证了value一定会+1成功并返回,利用unsafe.compareAndSwapInt(this, valueOffset, expect, update)保证了对于value修改的线程安全性。

CAS的缺点

  1.存在ABA情况。CAS在操作时,需要比较值有没有发生变化,没有变化则更新,如果一个变量由A变成了B,再由B变回了A,那么CAS在检查时,就是发现该变量的值没有发生改变,但是实际上却变化了。从Java1.5开始。JDK在atomic提供了一个AtomicStampedReference类来解决ABA问题,这个类的compareAndSet(expectedReference, newReference,expectedStamp, newStamp)方法,会首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值修改为给定的新值。

  2.循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

  3.只能保证一个共享变量的原子操作。当对一个共享变量进行操作时,我们可以采用CAS方式来保证变量的线程安全,当有多个变量时,CAS就无法保证操作的原子性了,这个时候就只能用锁或者采用AtomicReference类来保证引用对象之间的原子性,AtomicReference支持把多个变量放到一个对象里进行CAS操作。

转载于:https://www.cnblogs.com/Joe-Go/p/9771452.html

你可能感兴趣的文章
Latest Common Ancestor
查看>>
getByClass--获取指定标签且class为指定的所有元素
查看>>
三点顺序
查看>>
拟物化设计与扁平化设计
查看>>
PS小实验-去除水印
查看>>
IS-IS完整笔记
查看>>
★如何解释特修斯之船问题?
查看>>
玩转PS路径,轻松画logo!
查看>>
Python简介及学习
查看>>
!!!后续博客写到简书 + 博客园留博客目录
查看>>
小白学数据分析-----> 利用SPSS对DAU/MAU进行比率分析
查看>>
js中this与srcElement的区别
查看>>
第一个MyBatis程序
查看>>
.net 数据库缓存依赖:【实例与解释】
查看>>
Android Service小试 启动和停止service
查看>>
一天一个设计模式:适配器模式
查看>>
第5题 查找字符串中的最长回文字符串---Manacher算法
查看>>
HDOJ 1069 Monkey and Banana 解题报告
查看>>
11锚点与图像对象
查看>>
Ios中比较两个日期之间的时间差距
查看>>