CAS导致ABA问题原因以及解决办法

129 阅读2分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

ABA问题

image.png ABA问题是怎么产生的

线程工作的时间差

CAS会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。

尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。 原子引用案例(user实体类) 原子引用类AtomicReference<对象等>()

package com.wsx.aba;

import java.util.concurrent.atomic.AtomicReference;

        class User{
            private String name;
            private Integer age;

            public User(String name, Integer age) {
                this.name = name;
                this.age = age;
            }

            public User() {

            }

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }

            public Integer getAge() {
                return age;
            }

            public void setAge(Integer age) {
                this.age = age;
            }

            @Override
            public String toString() {
                return "User{" +
                        "name='" + name + ''' +
                        ", age=" + age +
                        '}';
            }
        }

        public class AtomicReferenceDemo {
            public static void main(String[] args) {
                User user1 = new User("zhangsan",22);
                User user2 = new User("lisi",24);

                AtomicReference<User> objectAtomicReference = new AtomicReference<>();

                objectAtomicReference.set(user1);

                System.out.println(objectAtomicReference.compareAndSet(user1, user2));
                System.out.println(objectAtomicReference.get().toString());
                System.out.println(objectAtomicReference.compareAndSet(user1, user2));
                System.out.println(objectAtomicReference.get().toString());
            }
        }

解决 理解原子引用+新增一种机制,那就是修改版本号(类似时间戳)

ABA问题的产生与解决(注意看注释)

类名AtomicStampedReference的带版本号式操作

package com.wsx.aba;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

        public class SloveAba {
            static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
            static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

            public static void main(String[] args) {
                System.out.println("ABA问题的产生");
                new Thread(()->{
                    atomicReference.compareAndSet(100,101);
                    atomicReference.compareAndSet(101,100);
                    System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
                },"t1").start();

                new Thread(()->{
                    try {
                        //为了让t1完成一次ABA操作
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    atomicReference.compareAndSet(100,2019);

                    System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
                },"t2").start();

                //主线程睡眠等待上限操作完成
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("ABA问题的解决");


                new Thread(()->{
                    int stamp = atomicStampedReference.getStamp();
                    //这里睡一秒是为了t3线程和t4线程的起点一致,版本号都为1
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());
                    //ABA带版本号操作
                    atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
                    System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());
                    atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
                    System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());

                },"t3").start();

                new Thread(()->{
                    int stamp = atomicStampedReference.getStamp();
                    //这里睡两秒是为了让t3线程完成ABA操作
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //这里的stamp是默认没被修改的版本号,如果被其他线程修改过那么这里版本号就开始落后,然后修改失败
                    boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

                    System.out.println(Thread.currentThread().getName()+"\t最新版本号"+atomicStampedReference.getStamp());
                    System.out.println(Thread.currentThread().getName()+"\t是否改动成功"+result);
                    System.out.println(Thread.currentThread().getName()+"\t最新值"+atomicStampedReference.getReference());
                },"t4").start();
            }

        }