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

Posted 小志的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发多线程编程——原子类AtomicInteger的ABA问题及原子更新引用相关的知识,希望对你有一定的参考价值。

一、ABA问题的概述

  • CAS会导致“ABA”问题
  • CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当前时刻比较并替换,那么在这个时间差类会导致数据的变化。
  • 例如:一个线程 t1 从内存位置V中取出A,这个时候另一个线程 t2 也从内存中取出A,并且线程 t2 进行了一些操作将值变成了B,然后线程 t2 又将V位置的数据变成A,这时候线程t1 进行CAS操作发现内存中仍然是A,然后线程 t1 操作成功。
  • 尽管线程t1的CAS操作成功,但是不代表这个过程就是没有问题的。

二、ABA问题的产生代码示例

  • 代码

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    /**
     * @description:  ABA问题的产生代码示例
     * @author: xz
     */
    public class ABADemo 
        private static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
    
        public static void main(String[] args) 
            System.out.println("===以下是ABA问题的产生===");
            new Thread(()->
                atomicReference.compareAndSet(100,101);
                atomicReference.compareAndSet(101,100);
            ,"t1").start();
    
            new Thread(()->
                //先暂停1秒 保证完成ABA
                try  TimeUnit.SECONDS.sleep(1);  catch (InterruptedException e)  e.printStackTrace(); 
                System.out.println(atomicReference.compareAndSet(100, 2019)+"\\t"+atomicReference.get());
            ,"t2").start();
    
        
    
    
  • 输出结果如下:

三、原子引用类(AtomicReference)

3.1、jdk1.8 API中的原子引用类截图如下:

3.2、原子引用类代码示例

  • 代码

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @description: 原子引用代码示例
     * @author: xz
     */
    @Getter
    @Setter
    @AllArgsConstructor
    @ToString
    class User
        private String name;
        private int age;
    
    
    public class AtomicReferenceDemo 
        public static void main(String[] args) 
    
            User zs = new User("张三", 29);
            User ls = new User("李四", 22);
    
            //AtomicReference中的泛型User表示原子引用类
            AtomicReference<User> userAtomicReference = new AtomicReference<>();
            userAtomicReference.set(zs);
            //当期望的引用对象zs和主内存中的引用对象zs相同,修改为更新的ls引用对象
            System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\\t"+userAtomicReference.get().toString());
            //当期望的引用对象zs和主内存中的引用对象zs相同,修改为更新的ls引用对象
            System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\\t"+userAtomicReference.get().toString());
        
    
    
    
  • 输出结果如下:

四、ABA问题的解决

4.1、ABA问题的解决思路

  • jdk1.8 API中的时间戳原子引用类(AtomicStampedReference)截图如下:

3.2、解决ABA问题的代码示例

  • 代码

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * @description:  通过AtomicStampedReference 时间戳原子引用类 解决ABA问题
     * @author: xz
     */
    public class ABADemo 
        private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1);
    
        public static void main(String[] args) 
            System.out.println("===以下是ABA问题的解决===");
    
            new Thread(()->
                int stamp = stampedReference.getStamp();
                System.out.println(Thread.currentThread().getName()+
                        "\\t 第1次版本号"+stamp+"\\t值是"+stampedReference.getReference());
    
                //暂停1秒钟t1线程
                try  TimeUnit.SECONDS.sleep(1);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
    
                stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+"\\t 第2次版本号"+stampedReference.getStamp()+"\\t值是"+stampedReference.getReference());
    
                stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+"\\t 第3次版本号"+stampedReference.getStamp()+"\\t值是"+stampedReference.getReference());
    
            ,"t1").start();
    
            new Thread(()->
                int stamp = stampedReference.getStamp();
                System.out.println(Thread.currentThread().getName()+
                        "\\t 第1次版本号"+stamp+"\\t值是"+stampedReference.getReference());
    
                //保证线程t1完成1次ABA
                try  TimeUnit.SECONDS.sleep(3);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName()+"\\t 修改成功否"+result+"\\t最新版本号"+stampedReference.getStamp());
                System.out.println("最新的值\\t"+stampedReference.getReference());
            ,"t2").start();
    
        
    
    
  • 输出结果如下:

以上是关于Java并发多线程编程——原子类AtomicInteger的ABA问题及原子更新引用的主要内容,如果未能解决你的问题,请参考以下文章

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

java并发编程要点速览(Java并发容器和框架,原子操作类,并发工具类)

学了这么久的高并发编程,连Java中的并发原子类都不知道?

「死磕Java并发编程」说说Java Atomic 原子类的实现原理

「死磕Java并发编程」说说Java Atomic 原子类的实现原理

「死磕Java并发编程」说说Java Atomic 原子类的实现原理