栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【JDK源码】并发原子类AtomicStampedReference

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【JDK源码】并发原子类AtomicStampedReference

文章目录
      • 简介
      • ABA
      • 内部类
      • 构造方法
      • compareAndSet()方法
      • 案例
      • 总结

简介

AtomicStampedReference是java并发包下提供的一个原子类,它能解决其它原子类无法解决的ABA问题,比如AtomicInteger存在ABA问题

ABA

ABA问题发生在多线程环境中,当某线程连续读取同一块内存地址两次,两次得到的值一样,它简单地认为“此内存地址的值并没有被修改过”,然而,同时可能存在另一个线程在这两次读取之间把这个内存地址的值从A修改成了B又修改回了A,这时还简单地认为“没有修改过”显然是错误的。

比如,两个线程按下面的顺序执行:

(1)线程1读取内存位置X的值为A;

(2)线程1阻塞了;

(3)线程2读取内存位置X的值为A;

(4)线程2修改内存位置X的值为B;

(5)线程2修改又内存位置X的值为A;

(6)线程1恢复,继续执行,比较发现还是A把内存位置X的值设置为C;

可以看到,针对线程1来说,第一次的A和第二次的A实际上并不是同一个A。

ABA问题通常发生在无锁结构中,用代码来表示上面的过程大概就是这样:

public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(1);
    new Thread(new Runnable() {
        @Override
        public void run() {
            int value = atomicInteger.get();
            System.out.println("线程1读到value="+value);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (atomicInteger.compareAndSet(value,3)) {
                System.out.println("线程1修改value从"+value+"到3");
            }
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            int value = atomicInteger.get();
            System.out.println("线程2读到value="+value);
            if (atomicInteger.compareAndSet(value,2)) {
                System.out.println("线程2修改value从"+value+"到2");

                value = atomicInteger.get();
                System.out.println("线程2读到value="+value);
                if (atomicInteger.compareAndSet(value,1)){
                    System.out.println("线程2修改value从"+value+"到1");
                }
            }
        }
    }).start();
}

输出

线程1读到value=1
线程2读到value=1
线程2修改value从1到2
线程2读到value=2
线程2修改value从2到1
线程1修改value从1到3
内部类

AtomicStampedReference

private static class Pair {
    final T reference;// 元素值
    final int stamp;  // 版本号
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static  Pair of(T reference, int stamp) {
        return new Pair(reference, stamp);
    }
}

将元素值和版本号绑定在一起,存储在Pair的reference和stamp(邮票、戳的意思)中。

构造方法
public AtomicStampedReference(V initialRef, int initialStamp) {
    // 也就是初始化的时候必须传入元素值和版本号
    pair = Pair.of(initialRef, initialStamp);
}
compareAndSet()方法
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    // 获取当前的(元素值和版本号)对
    Pair current = pair;
    return
        // 元素没变
        expectedReference == current.reference &&
        // 版本号没变
        expectedStamp == current.stamp &&
        // 新引用等于旧引用
        ((newReference == current.reference &&
          // 新版本号等于旧版本号
          newStamp == current.stamp) ||
         // 构造新的Pair对象并CAS更新
         casPair(current, Pair.of(newReference, newStamp)));
}
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset =
    objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

private boolean casPair(Pair cmp, Pair val) {
    // 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

(1)如果元素值和版本号都没有变化,并且和新的也相同,返回true;

(2)如果元素值和版本号都没有变化,并且和新的不完全相同,就构造一个新的Pair对象并执行CAS更新pair。

  • AtomicStampedReference采用版本号来解决ABA
案例

使用AtomicStampedReference解决那个AtomicInteger带来的ABA问题

public static void main(String[] args) {
    AtomicStampedReference atomicInteger = new AtomicStampedReference<>(1,1);
    int[] stampHolder = new int[1];

    new Thread(new Runnable() {
        @Override
        public void run() {
            int  value = atomicInteger.get(stampHolder);
            // 一定要先保存原来的值
            int stamp = stampHolder[0];
            System.out.println("线程1读到value="+value+"、stamp="+stampHolder[0]);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1读到value="+value+"、stamp="+stampHolder[0]);
            // 这里变成stamp是为了传入之前的值,如果是stampHolder[0]则是最新的值也就没有意义了
            if (atomicInteger.compareAndSet(value,3,stamp,stampHolder[0]+1)) {
                System.out.println("线程1修改value从"+value+"到3、"+"stamp从"+stampHolder[0]+"到"+(stampHolder[0]+1));
            }else{
                System.out.println("线程1修改失败");
            }
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            int  value = atomicInteger.get(stampHolder);
            System.out.println("线程2读到value="+value+"、stamp="+stampHolder[0]);
            if (atomicInteger.compareAndSet(value,2,stampHolder[0],stampHolder[0]+1)) {
                System.out.println("线程2修改value从"+value+"到2、"+"stamp从"+stampHolder[0]+"到"+(stampHolder[0]+1));

                value = atomicInteger.get(stampHolder);
                System.out.println("线程2读到value="+value+"、stamp="+stampHolder[0]);
                if (atomicInteger.compareAndSet(value,1,stampHolder[0],stampHolder[0]+1)){
                    System.out.println("线程2修改value从"+value+"到1、"+"stamp从"+stampHolder[0]+"到"+(stampHolder[0]+1));
                    value = atomicInteger.get(stampHolder);
                    System.out.println("线程2读到value="+value+"、stamp="+stampHolder[0]);
                }
            }
        }
    }).start();
}

可以看到线程1最后更新1到3时失败了,因为这时版本号也变了,成功解决了ABA的问题。

线程2读到value=1、stamp=1
线程1读到value=1、stamp=1
线程2修改value从1到2、stamp从1到2
线程2读到value=2、stamp=2
线程2修改value从2到1、stamp从2到3
线程2读到value=1、stamp=3
线程1读到value=1、stamp=3
线程1修改失败
总结

(1)在多线程环境下使用无锁结构要注意ABA问题;

(2)ABA的解决一般使用版本号来控制,并保证数据结构使用元素值来传递,且每次添加元素都新建节点承载元素值;

(3)AtomicStampedReference内部使用Pair来存储元素值及其版本号;

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/458363.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号