赞
踩
原文链接:什么叫做CAS – 编程屋
CAS(compare and swap)比较并交换,在平时开发中其实很多底层都是用cas来实现的,像原子类的底层原理就是cas,乐观锁的底层原理也是cas。原子类的用法可见下面这篇博客
cas的特点:当多个线程同时使用cas去更新一个变量的时候,只有其中一个线程能够操作成功,其他的线程都能够操作失败,但是更新失败的线程不会阻塞,但失败的线程会自旋尝试更新(默认都有尝试更新的次数)。
CAS中的核心操作:内存值V、预期值A、要修改的值B。当且仅当预期值A和当前的内存值V相等时才会将内存值修改为B。
CAS带来的ABA问题
比如线程1已经将利用CAS修改变量值A,但是在修改之前其他线程已经将A变成了B,然后又变成了A,即A->B->A问题。因为线程1在操作的时候发现值依然为A,所以根据CAS的机制会操作成功,但是其实这个值已经被其他线程修改过了,只是线程1不知道而已,这就导致了ABA问题。
demo实例:
- private static AtomicReference<Integer> atomicInteger = new AtomicReference(100);
-
-
- public static void main(String[] args) throws InterruptedException {
- System.out.println("=================以下是ABA问题产生==========================");
- new Thread(() -> {
- atomicInteger.compareAndSet(100, 101);
- atomicInteger.compareAndSet(101, 100);
- },"t1").start();
-
-
- new Thread(() -> {
- try {
- //暂停1s中t2线程,保证了上面的t1线程完成了一次ABA操作(当main
- // 线程启动之后,t1线程和t2线程不知道谁先执行,所以让t2线程先睡1秒,这样即使t2线程先执行的情况下。睡1s时,也会被t1线程抢到完成一次ABA的操作)
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- boolean result = atomicInteger.compareAndSet(100, 2022);
- System.out.println("是否修改成功:" + result +"\t"+"修改之后的值为"+atomicInteger.get());
- },"t2").start();
-
-
-
- }
由demo可以看到两个线程,第一个线程将100改为101后,又将101改为了100,线程2依然可以将100改为其他的值(2022)。所以说CAS的确带来了ABA问题。
在java中,AtomicStampedReference通过包装[E,Integer]的元组来对象对象标记版本戳stamp,从而避免ABA问题。
demo示例:
- private static AtomicReference<Integer> atomicInteger = new AtomicReference(100);
- private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);
-
- public static void main(String[] args) throws InterruptedException {
- System.out.println("=================以下是ABA问题产生==========================");
- new Thread(() -> {
- atomicInteger.compareAndSet(100, 101);
- atomicInteger.compareAndSet(101, 100);
- },"t1").start();
-
-
- new Thread(() -> {
- try {
- //暂停1s中t2线程,保证了上面的t1线程完成了一次ABA操作(当main
- // 线程启动之后,t1线程和t2线程不知道谁先执行,所以让t2线程先睡1秒,这样即使t2线程先执行的情况下。睡1s时,也会被t1线程抢到完成一次ABA的操作)
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- boolean result = atomicInteger.compareAndSet(100, 101);
- System.out.println("是否修改成功:" + result +"\t"+"修改之后的值为"+atomicInteger.get());
- },"t2").start();
-
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {}
-
- System.out.println("=================以下是ABA问题的解决==========================");
-
- new Thread(() -> {
- //拿到此时版本号
- int stamp = atomicStampedReference.getStamp();
- System.out.println(Thread.currentThread().getName()+"\t第1次版本号为"+stamp);
-
- //暂停1s钟t3线程,让cpu去调度t4线程拿到同一个初始版本号
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {}
- //将100设置成101并增加版本号
- atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),
- atomicStampedReference.getStamp() + 1);
- System.out.println(Thread.currentThread().getName()+"\t第2次版本号为"+atomicStampedReference.getStamp());
- //将101设置成100并增加版本号
- atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),
- atomicStampedReference.getStamp() + 1);
-
- System.out.println(Thread.currentThread().getName()+"\t第3次版本号为"+atomicStampedReference.getStamp());
-
- },"t3线程").start();
-
- new Thread(() -> {
- //拿到此时版本号
- int stamp = atomicStampedReference.getStamp();
- System.out.println(Thread.currentThread().getName()+"\t第1次版本号为"+stamp);
- //暂停t4线程3秒钟,保证t3线程完成一次ABA操作
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { }
- //将100设置成101并增加版本号
- boolean refResult = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp + 1);
-
- System.out.println("是否修改成功:" + refResult +"\t"+"实际最新值为"+atomicStampedReference.getReference()+"\t"+"当前实际版本号为:"+atomicStampedReference.getStamp());
-
- },"t4线程").start();
控制台输出:
可以看出,加入了版本号控制,解决了ABA的问题
1)ABA问题
2)循环时间长,开销较大
以上只是部分内容,为了维护方便,本文已迁移到新地址:什么叫做CAS – 编程屋
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。