Java多线程进阶(13)—— J.U.C之atomic框架:AtomicReference

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

 

一、AtomicReference简介

AtomicReference,顾名思义,就是以原子方式更新对象引用。

可以看到,AtomicReference持有一个对象的引用—— value ,并通过Unsafe类来操作该引用:

为什么需要AtomicReference?难道多个线程同时对一个引用变量赋值也会出现并发问题?
引用变量的赋值本身没有并发问题,也就是说对于引用变量var ,类似下面的赋值操作本身就是原子操作:
Foo var = ... ;
AtomicReference的引入是为了可以用一种类似乐观锁的方式操作共享资源,在某些情景下以提升性能。

我们知道,当多个线程同时访问共享资源时,一般需要以加锁的方式控制并发:

    volatile Foo sharedValue = value;
    Lock lock = new ReentrantLock();
    
    lock.lock();
    try{
        // 操作共享资源sharedValue
    }
    finally{
        lock.unlock();
    }

上述访问方式其实是一种对共享资源加 悲观锁 的访问方式。

而AtomicReference提供了 以无锁方式访问共享资源 的能力,看看如何通过AtomicReference保证线程安全,来看个具体的例子:

    public class AtomicRefTest {
        public static void main(String[] args) throws InterruptedException {
            AtomicReference<Integer> ref = new AtomicReference<>(new Integer(1000));
    
            List<Thread> list = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                Thread t = new Thread(new Task(ref), "Thread-" + i);
                list.add(t);
                t.start();
            }
    
            for (Thread t : list) {
                t.join();
            }
    
            System.out.println(ref.get());    // 打印2000
        }
    
    }
    
    class Task implements Runnable {
        private AtomicReference<Integer> ref;
    
        Task(AtomicReference<Integer> ref) {
            this.ref = ref;
        }
    
        @Override
        public void run() {
            for (; ; ) {    //自旋操作
                Integer oldV = ref.get();   
                if (ref.compareAndSet(oldV, oldV + 1))  // CAS操作 
                    break;
            }
        }
    }

上述示例,最终打印“2000”。

该示例并没有使用锁,而是使用 自旋+CAS 的无锁操作保证共享变量的线程安全。1000个线程,每个线程对金额增加1,最终结果为2000,如果线程不安全,最终结果应该会小于2000。

通过示例,可以总结出AtomicReference的一般使用模式如下:

    AtomicReference<Object> ref = new AtomicReference<>(new Object());
    Object oldCache = ref.get();
    
    // 对缓存oldCache做一些操作
    Object newCache  =  someFunctionOfOld(oldCache); 
    
    // 如果期间没有其它线程改变了缓存值,则更新
    boolean success = ref.compareAndSet(oldCache , newCache);

上面的代码模板就是AtomicReference的常见使用方式,看下 compareAndSet 方法:

该方法会将入参的 expect 变量所指向的对象和AtomicReference中的引用对象进行比较,如果两者指向同一个对象,则将AtomicReference中的引用对象重新置为 update ,修改成功返回true,失败则返回false。也就是说, AtomicReference其实是比较对象的引用 。

二、AtomicReference接口/类声明

类声明

接口声明

三、CAS操作可能存在的问题

CAS操作可能存在 ABA 的问题,就是说:
假如一个值原来是A,变成了B,又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了。

一般来讲这并不是什么问题,比如数值运算,线程其实根本不关心变量中途如何变化,只要最终的状态和预期值一样即可。

但是,有些操作会依赖于对象的变化过程,此时的解决思路一般就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A - 2B - 3A。

四、AtomicStampedReference的引入

AtomicStampedReference就是上面所说的加了版本号的AtomicReference。

4.1 AtomicStampedReference原理

先来看下如何构造一个AtomicStampedReference对象,AtomicStampedReference只有一个构造器:

可以看到,除了传入一个初始的引用变量 initialRef 外,还有一个 initialStamp 变量, initialStamp 其实就是版本号(或者说时间戳),用来唯一标识引用变量。

在构造器内部,实例化了一个 Pair 对象, Pair 对象记录了对象引用和时间戳信息,采用int作为时间戳,实际使用的时候,要保证时间戳唯一(一般做成自增的),如果时间戳如果重复,还会出现 ABA 的问题。

AtomicStampedReference的所有方法,其实就是Unsafe类针对这个 Pair 对象的操作。
和AtomicReference相比,AtomicStampedReference中的每个引用变量都带上了pair.stamp这个版本号,这样就可以解决CAS中的ABA问题了。

4.2 AtomicStampedReference使用示例

来看下AtomicStampedReference的使用:

    AtomicStampedReference<Foo>  asr = new AtomicStampedReference<>(null,0);  // 创建AtomicStampedReference对象,持有Foo对象的引用,初始为null,版本为0
    
    int[] stamp=new  int[1];
    Foo  oldRef = asr.get(stamp);   // 调用get方法获取引用对象和对应的版本号
    int oldStamp=stamp[0];          // stamp[0]保存版本号
    
    asr.compareAndSet(oldRef, null, oldStamp, oldStamp + 1)   //尝试以CAS方式更新引用对象,并将版本号+1

上述模板就是AtomicStampedReference的一般使用方式,注意下 compareAndSet 方法:

我们知道,AtomicStampedReference内部保存了一个pair对象,该方法的逻辑如下:

  1. 如果AtomicStampedReference内部pair的引用变量、时间戳 与 入参 expectedReference 、 expectedStamp 都一样,说明期间没有其它线程修改过AtomicStampedReference,可以进行修改。此时,会创建一个新的Pair对象(casPair方法,因为Pair是Immutable类)。

但这里有段优化逻辑,就是如果 newReference == current.reference && newStamp == current.stamp,说明用户修改的新值和AtomicStampedReference中目前持有的值完全一致,那么其实不需要修改,直接返回true即可。

4.3 AtomicStampedReference接口声明

五、AtomicMarkableReference

我们在讲 ABA 问题的时候,引入了AtomicStampedReference。

AtomicStampedReference可以给引用加上版本号,追踪引用的整个变化过程,如:
A -> B -> C -> D - > A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了3次。

但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心 是否更改过 ,所以就有了 AtomicMarkableReference :

可以看到,AtomicMarkableReference的唯一区别就是 不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过 。

从语义上讲,AtomicMarkableReference对于那些不关心引用变化过程,只关心引用变量是否变化过的应用会更加友好。

AtomicMarkableReference接口声明

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的`java.util.concurrent.atomic.AtomicReference`提供了一种线程安全的方式来更新对象引用。它通过使用CAS(Compare-And-Swap)算法实现了原子性的操作。 下面是一个简单的案例,展示如何使用`AtomicReference`类: ```java import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceExample { public static void main(String[] args) { // 初始化AtomicReference对象 AtomicReference<String> atomicReference = new AtomicReference<>("Hello"); // 获取当前对象引用的值 String currentValue = atomicReference.get(); System.out.println("Current value: " + currentValue); // 比较并替换 boolean updated = atomicReference.compareAndSet("Hello", "World"); System.out.println("Value updated: " + updated); // 获取更新后的值 String updatedValue = atomicReference.get(); System.out.println("Updated value: " + updatedValue); } } ``` 在上面的示例中,我们首先创建了一个`AtomicReference`对象,并初始化为字符串`"Hello"`。然后我们使用`compareAndSet`方法比较当前对象引用的值是否为`"Hello"`,如果是,则将其替换为`"World"`。最后,我们获取更新后的值,并输出到控制台。 需要注意的是,`AtomicReference`类提供了许多其他有用的方法,如`set`、`getAndSet`、`weakCompareAndSet`等,可以根据具体的需求选择使用。 总之,`AtomicReference`类是Java中一种非常有用的线程安全对象引用类,可以避免多个线程同时修改对象引用时出现的竞争条件问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值