美文网首页
Java- CAS带来的 ABA问题及解决方法的代码实例

Java- CAS带来的 ABA问题及解决方法的代码实例

作者: 行走中的3卡 | 来源:发表于2022-04-11 15:20 被阅读0次

1. CAS(Compare And Swap)导致的ABA问题
代码实例。
主要操作方法是 AtomicReference.compareAndSet(oldvalue, newValule)
同时使用了 CountDownLatch(类似计数器的功能)
(从日志也可以看出,线程的执行并不是按照我们创建或启动的顺序的)

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

public class AbaPro {

    private static final Random RANDOM = new Random();
    private static final String B = "B";
    private static final String A = "A";
    public static final AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<>(A);


    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch startLatch = new CountDownLatch(1);

        Thread[] threads = new Thread[20];
        for (int i=0; i < 20; i++){
            threads[i] = new Thread(){
                @Override
                public void run() {
                    String oldValue = ATOMIC_REFERENCE.get();
                    System.out.println(Thread.currentThread().getName()+ " oldValue: "+ oldValue);
                    try {
                        startLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+ " 唤醒了 ");
                    try {
                        Thread.sleep(RANDOM.nextInt()&500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+ " 等待一段时间后现在的值: "+ ATOMIC_REFERENCE.get());
                    // 1. 在这里: 第一次改 & 第三次改 :  A -> B
                    if (ATOMIC_REFERENCE.compareAndSet(oldValue, B )){
                        System.out.println(Thread.currentThread().getName()+ " 已经对原始值进行了修改,此时值为: "+ ATOMIC_REFERENCE.get());
                    }
                }
            };
            threads[i].start();
        }
        System.out.println(Thread.currentThread().getName() +" 即将 count down");
        startLatch.countDown();
        System.out.println(Thread.currentThread().getName() +" count down done");

        Thread.sleep(200);
        System.out.println(Thread.currentThread().getName() +" sleep 200ms done");

        new Thread(){

            @Override
            public void run() {
                try {
                    Thread.sleep(RANDOM.nextInt() & 200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String oldVal = ATOMIC_REFERENCE.get();
                // 2. 在这里: 第二次改   B -> A
                while (!ATOMIC_REFERENCE.compareAndSet(ATOMIC_REFERENCE.get(), A));
                System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
            }

        }.start();
    }

}

日志打印:

Thread-0 oldValue: A
Thread-1 oldValue: A
Thread-2 oldValue: A
Thread-9 oldValue: A
Thread-13 oldValue: A
Thread-6 oldValue: A
Thread-8 oldValue: A
Thread-7 oldValue: A
Thread-12 oldValue: A
Thread-10 oldValue: A
Thread-5 oldValue: A
Thread-4 oldValue: A
Thread-3 oldValue: A
Thread-19 oldValue: A
Thread-15 oldValue: A
Thread-11 oldValue: A
Thread-16 oldValue: A
Thread-18 oldValue: A
Thread-17 oldValue: A
main 即将 count down
main count down done
Thread-2 唤醒了 
Thread-14 oldValue: A
Thread-1 唤醒了 
Thread-14 唤醒了 
Thread-13 唤醒了 
Thread-9 唤醒了 
Thread-0 唤醒了 
Thread-8 唤醒了 
Thread-7 唤醒了 
Thread-12 唤醒了 
Thread-6 唤醒了 
Thread-4 唤醒了 
Thread-3 唤醒了 
Thread-5 唤醒了 
Thread-10 唤醒了 
Thread-19 唤醒了 
Thread-11 唤醒了 
Thread-15 唤醒了 
Thread-17 唤醒了 
Thread-18 唤醒了 
Thread-16 唤醒了 
Thread-13 等待一段时间后现在的值: A
Thread-13 已经对原始值进行了修改,此时值为: B
Thread-12 等待一段时间后现在的值: B
Thread-8 等待一段时间后现在的值: B
Thread-5 等待一段时间后现在的值: B
Thread-6 等待一段时间后现在的值: B
Thread-7 等待一段时间后现在的值: B
Thread-3 等待一段时间后现在的值: B
Thread-1 等待一段时间后现在的值: B
main sleep 200ms done
Thread-14 等待一段时间后现在的值: B
Thread-18 等待一段时间后现在的值: B
Thread-0 等待一段时间后现在的值: B
Thread-15 等待一段时间后现在的值: B
Thread-4 等待一段时间后现在的值: B
Thread-2 等待一段时间后现在的值: B
Thread-20 已经将值 B 修改成原始值: A
Thread-10 等待一段时间后现在的值: A
Thread-10 已经对原始值进行了修改,此时值为: B
Thread-17 等待一段时间后现在的值: B
Thread-9 等待一段时间后现在的值: B
Thread-16 等待一段时间后现在的值: B
Thread-11 等待一段时间后现在的值: B
Thread-19 等待一段时间后现在的值: B

关键的日志在于:

Thread-13 已经对原始值进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
Thread-10 已经对原始值进行了修改,此时值为: B

2. 解决方案
java中提供了AtomicStampedReference来解决这个问题,它是基于版本或者是一种状态,在修改的过程中不仅对比值,也同时会对比版本号

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicStampedReference;

public class AbaProResolve {

    private static final Random RANDOM = new Random();
    private static final String B = "B";
    private static final String A = "A";

    private static final AtomicStampedReference<String> ATOMIC_STAMPED_REFERENCE = new AtomicStampedReference<>(A, 0);

    public static void main(String[] args) throws InterruptedException {

        final CountDownLatch startLatch = new CountDownLatch(1);
        Thread[] threads = new Thread[20];

        for (int i = 0; i < 20; i++) {
            threads[i] = new Thread() {

                @Override
                public void run() {
                    String oldValue = ATOMIC_STAMPED_REFERENCE.getReference();
                    int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();

                    try {
                        startLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(RANDOM.nextInt() & 500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (ATOMIC_STAMPED_REFERENCE.compareAndSet(oldValue, B, stamp, stamp + 1)) {
                        System.out.println(Thread.currentThread().getName() + " 已经对原始值: " + oldValue + " 进行了修改,此时值为: " + ATOMIC_STAMPED_REFERENCE.getReference());
                    }
                }
            };
            threads[i].start();
        }
        Thread.sleep(200);
        startLatch.countDown();

        new Thread() {
            @Override
            public void run() {

                try {
                    Thread.sleep(RANDOM.nextInt() & 200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
                String oldVal = ATOMIC_STAMPED_REFERENCE.getReference();
                while (!ATOMIC_STAMPED_REFERENCE.compareAndSet(B, A, stamp, stamp + 1)) {
                    stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
                }
                System.out.println(Thread.currentThread().getName() + " 已经将值 " + oldVal + " 修改成原始值: A");


            }
        }.start();
    }
}

日志打印: A -> B, B -> A
Thread-6 已经对原始值: A 进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A

参考:
Java并发编程原理与实战四十三:CAS ---- ABA问题 - pony1223 - 博客园 (cnblogs.com)

相关文章

  • Java- CAS带来的 ABA问题及解决方法的代码实例

    1. CAS(Compare And Swap)导致的ABA问题代码实例。主要操作方法是 AtomicRefere...

  • 细谈CAS与ABA

    题目:如何实现乐观锁(CAS),如何避免ABA问题? 这个题主要考查原子操作、悲观锁、乐观锁及ABA问题。 原子操...

  • 原子操作 CAS CompareAndSwap

    参考 Java CAS ABA问题发生的场景分析 提到了ABA问题 Unsafe$compareAndSwapIn...

  • CAS中的ABA问题

    补档CAS中的ABA问题。 要特别注意,常见的ABA问题有两种,要求能分别举例解释。 CAS的使用可参考:源码|并...

  • CAS 的ABA问题

    关于CAS操作有个经典的ABA问题,具体如下:假如线程I使用CAS修改初始值为A的变量X,那么线程I会首先去获取当...

  • JUC之ABA问题

    什么是ABA问题? ABA问题是由CAS而导致的一个问题 CAS算法实现一个重要前提需要取出内存中某时刻的数据并在...

  • CAS导致的ABA问题及解决

    Java并发--非阻塞同步 CAS问题引入 在并发问题中,最先想到的无疑是互斥同步,但线程阻塞和唤醒带来了很大的性...

  • CAS ABA问题

    java.util.concurrent包的最底层基础CAS技术,原理很简单。 CAS有3个操作数,内存值V,旧的...

  • 死磕 java并发包之AtomicStampedReferenc

    问题 (1)什么是ABA? (2)ABA的危害? (3)ABA的解决方法? (4)AtomicStampedRef...

  • 1.2.3JAVA锁相关

    ABA问题 在两个线程同时对一个资源进行CAS的时候,会导致ABA问题,就是在线程A进行了一次CAS,这个时候线程...

网友评论

      本文标题:Java- CAS带来的 ABA问题及解决方法的代码实例

      本文链接:https://www.haomeiwen.com/subject/uygisrtx.html