原子引用解决ABA问题

HeJin大约 2 分钟JavaJUC并发编程

ABA问题

狸猫换太子。

image-20210325164147766
image-20210325164147766
public class CasDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2021);
        /** 捣乱的线程 **/
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2022, 2021));
        System.out.println(atomicInteger.get());
        /** 期望的线程 **/
        System.out.println(atomicInteger.compareAndSet(2021, 2233));
        System.out.println(atomicInteger.get());
    }
}

结果:

true
2022
true
2021
true
2233
    
Process finished with exit code 0

对于我们平时写的SQL来说:乐观锁。

原子引用

image-20210325164124499
image-20210325164124499
public class CasDemo {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(2021, 1);
        new Thread(() ->{
            // 获得版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1==>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(2021, 2022
                    , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a2==>" + atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2022, 2021
                    , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a3==>" + atomicStampedReference.getStamp());
        },"a").start();
        new Thread(() ->{
            // 获得版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1==>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(2021, 2233
                    , stamp, stamp + 1));
            System.out.println("b2==>" + atomicStampedReference.getStamp());
        },"b").start();
    }
}

结果:

a1==>1
b1==>1
false
false
a2==>1
false
a3==>1
b2==>1
    
Process finished with exit code

结果有问题。

  1. 看compareAndSet的源码,里面是使用 == 进行比较的。
  2. 由于new的时候声明泛型肯定是装箱类,这个时候传入值类型将会自动装箱。
  3. 自动装箱的后果就是地址不一致,使用==判断的结果就为false。
  4. 总结:最好不使用原子类型,使用原子类型得保证比较时候传入的为同一个装箱类。

Integer使用了对象缓存机制,默认范国是-128~127。推荐使用静态工厂方法valueof获取对象实例,而不是new。因为valueof使用缓存,而new一定会创建新的对象分配新的内存空间。

image-20210325164051013
image-20210325164051013

把数字改小一点。

public class CasDemo {
    public static void main(String[] args) {
        // AtomicStampedReference 注意:泛型是一个包装类,对象的引用问题
        // 正常业务中,这里面都是一个个对象
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
        new Thread(() ->{
            // 获得版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1==>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1, 2
                                                 , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("a2==>" + atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2, 1
                                                                    , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a3==>" + atomicStampedReference.getStamp());
        },"a").start();
        new Thread(() ->{
            // 获得版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1==>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 6
                                                                    , stamp, stamp + 1));
            System.out.println("b2==>" + atomicStampedReference.getStamp());
        },"b").start();
    }
}

结果:

a1==>1
b1==>1
a2==>2
true
a3==>3
false
b2==>3
    
Process finished with exit code 0

原子引用和乐观锁的原理相同。