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

x33g5p2x  于2022-01-11 转载在 Java  
字(3.7k)|赞(0)|评价(0)|浏览(405)

一、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();

    }
}
  • 输出结果如下:

相关文章