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

Posted 小陈乱敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java- CAS带来的 ABA问题及解决方法的代码实例相关的知识,希望对你有一定的参考价值。

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)

本文转自 https://www.jianshu.com/p/9a634797ae32,如有侵权,请联系删除。

以上是关于Java- CAS带来的 ABA问题及解决方法的代码实例的主要内容,如果未能解决你的问题,请参考以下文章

Java高性能编程之CAS与ABA及解决方法

Java并发多线程编程——原子类AtomicInteger的ABA问题及原子更新引用

Java中CAS-ABA的问题解决方案

多线程 CAS 机制解析及应用( 原子类 . 自旋锁 )解决 ABA 问题

java中的CAS乐观锁概念以及CAS的问题;如何解决乐观锁ABA问题

CAS与ABA问题产生和解决