美文网首页
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问题及解决方法的代码实例

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