原子类 AtomicReference 详解

news2024/11/26 15:41:31

通过对 AtomicInteger、AtomicBoolean 和 AtomicLong 分析我们发现,这三个原子类只能对单个变量进行原子操作,那么我们如果要对多个变量进行原子操作,这三个类就无法实现了。那如果要进行多个变量进行原子操作呢?操作方式就是,先把 多个变量封装成一个类,然后通过 AtomicReference 进行操作。

众所周知,对象的引用其实是一个4字节的数字,代表着在JVM堆内存中的引用地址,对一个4字节数字的读取操作和写入操作本身就是原子性的,通常情况下,我们对对象引用的操作一般都是获取该引用或者重新赋值(写入操作),我们也没有办法对对象引用的4字节数字进行加减乘除运算,那么为什么JDK要提供AtomicReference类用于支持引用类型的原子性操作呢?

1、AtomicReference的应用场景


这里通过设计一个个人银行账号资金变化的场景,逐渐引入AtomicReference的使用,该实例有些特殊,需要满足如下几点要求。

 个人账号被设计为不可变对象,一旦创建就无法进行修改。
个人账号类只包含两个字段:账号名、现金数字。
为了便于验证,我们约定个人账号的现金只能增多而不能减少。
根据前两个要求,我们简单设计一个代表个人银行账号的Java类DebitCard,该类将被设计为不可变。

 
public class DebitCard {
    private final String name;
    private final int account;
    public DebitCard(String name, int account) {
        this.name = name;
        this.account = account;
    }
    public String getName() {
        return name;
    }
 
    public int getAccount() {
        return account;
    }
 
    @Override
    public String toString() {
        return "DebitCard {name=\""+name+"\"," +
                "account="+account+"}";
    }
}
1.1、通过 volatile 修饰的多线程 
 
public class AtomicReferenceDemo1 {
 
    // 定义为 volatile 修饰的变量
    volatile static  DebitCard debitCard = new DebitCard("zhangSan", 10);
 
    public static void main(String[] args) {
        IntStream.range(0, 10).forEach(i -> {
            new Thread(() -> {
                while (true){
                    // 读取全局的 debitCard 对象
                    final DebitCard debitCard1 = debitCard;
                    // 基于全局的 debitCard 加10构建一个新的对象
                    DebitCard newDc = new DebitCard(debitCard1.getName(), debitCard1.getAccount() + 10);
                    // 把新建的都行赋值给 全局的变量
                    debitCard = newDc;
                    System.out.println(newDc);
                    try {
                        TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(20));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "T-" + i).start();
        });
    }
}

根据运行结果我们发现,出现了两处存在问题的地方(上图标红的地方),这个是什么造成的呢?这就涉及到我们之前讲过的,虽然被 volatile 关键字修饰的变量每次更改都可以立即被其他线程看到,但是我们针对对象引用的修改其实至少包含了如下两个步骤,获取该引用和改变该引用 每一个步骤都是原子性的操作,但组合起来就无法保证原子性了。

1.2、通过加锁的多线程


针对上面的问题,我们首先想到的可能是通过加锁(synchronized 或 Lock )解决,如下代码所示:

// 加 锁 操作
synchronized (AtomicReferenceDemo1.class) {
    // 读取全局的 debitCard 对象
   final DebitCard debitCard1 = debitCard;
   // 基于全局的 debitCard 加10构建一个新的对象
 DebitCard newDc = new DebitCard(debitCard1.getName(), debitCard1.getAccount() + 10);
// 把新建的都行赋值给 全局的变量
  debitCard = newDc;
  System.out.println(newDc);
  try {
                            TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
} catch (InterruptedException e) {
    e.printStackTrace();
}
}
1.3、 AtomicReference的非阻塞解决方案


第2小节中的方案似乎满足了我们的需求,但是它却是一种阻塞式的解决方案,同一时刻只能有一个线程真正在工作,其他线程都将陷入阻塞,因此这并不是一种效率很高的解决方案,这个时候就可以利用 AtomicReference 的非阻塞原子性解决方案提供更加高效的方式了。

public class AtomicReferenceDemo1 {
 
    static AtomicReference<DebitCard> ref = new AtomicReference(new DebitCard("zhangSan", 10));
 
    public static void main(String[] args) {
        IntStream.range(0, 10).forEach(i -> {
            new Thread(() -> {
                while (true){
                    DebitCard debitCard = ref.get();
                    DebitCard newDc = new DebitCard(debitCard.getName(), debitCard.getAccount() + 10);
                    if(ref.compareAndSet(debitCard, newDc)){
                        System.out.println(Thread.currentThread().getName() + " 当前值为: " + newDc.toString() + " " + System.currentTimeMillis());
                    }
                    long sleepValue = (long) (Math.random() * 10000);
                    try {
                        Thread.sleep(sleepValue);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "T-" + i).start();
        });
    }
}

2、AtomicReference的基本用法


2.1、创建 AtomicReference


  AtomicReference是一个泛型类,它的构造与其他原子类型的构造一样,也提供了无参和一个有参的构造函数。

 AtomicReference():当使用无参构造函数创建AtomicReference对象的时候,需要再次调用set()方法为AtomicReference内部的value指定初始值。
 AtomicReference(V initialValue):创建AtomicReference对象时顺便指定初始值。


2.2、常用方法

  • compareAndSet(V expect, V update):原子性地更新AtomicReference内部的value值,其中expect代表当前AtomicReference的value值,update则是需要设置的新引用值。该方法会返回一个boolean的结果,当expect和AtomicReference的当前值不相等时,修改会失败,返回值为false,若修改成功则会返回true。
  • getAndSet(V newValue):原子性地更新AtomicReference内部的value值,并且返回AtomicReference的旧值。
  • getAndUpdate(UnaryOperator<V> updateFunction):原子性地更新value值,并且返回AtomicReference的旧值,该方法需要传入一个Function接口。
  • updateAndGet(UnaryOperator<V> updateFunction):原子性地更新value值,并且返回AtomicReference更新后的新值,该方法需要传入一个Function接口。
  • getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction):原子性地更新value值,并且返回AtomicReference更新前的旧值。该方法需要传入两个参数,第一个是更新后的新值,第二个是BinaryOperator接口。
  • getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction):原子性地更新value值,并且返回AtomicReference更新前的旧值。该方法需要传入两个参数,第一个是更新后的新值,第二个是BinaryOperator接口。
  • accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction):原子性地更新value值,并且返回AtomicReference更新后的值。该方法需要传入两个参数,第一个是更新的新值,第二个是BinaryOperator接口。
  • get():获取AtomicReference的当前对象引用值。
  • set(V newValue):设置AtomicReference最新的对象引用值,该新值的更新对其他线程立即可见。
  • lazySet(V newValue):设置AtomicReference的对象引用值。lazySet方法的原理已经在AtomicInteger中介绍过了,这里不再赘述。


3、AtomicReference的内幕


在AtomicReference类中,最关键的方法为compareAndSet(),下面来一探该方法的内幕。

AtomicReference中的方法
 

public final boolean compareAndSet(V expect, V update) {
  return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

 Unsafe中的代码

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
import java.util.concurrent.atomic.AtomicReference;
 
public class Main {
    public static void main(String[] args) {
        AtomicReference<String> atomicRef = new AtomicReference<>("Hello");
 
        String oldValue = atomicRef.get();
        System.out.println("Old Value: " + oldValue);
 
        atomicRef.set("World");
 
        String updatedValue = atomicRef.get();
        System.out.println("Updated Value: " + updatedValue);
 
        boolean exchanged = atomicRef.compareAndSet("World", "Hello");
        System.out.println("Exchange Result: " + exchanged);
 
        String finalValue = atomicRef.get();
        System.out.println("Final Value: " + finalValue);
    }
}

输出结果:

Old Value: Hello
Updated Value: World
Exchange Result: true
Final Value: Hello

4.AtomicBoolean

// 无参构造AtomicBoolean,默认为false
AtomicBoolean ab = new AtomicBoolean();
// 更改失败
assert !ab.compareAndSet(true, false);
// ab.get()==false
assert !ab.get();
// 更改成功
assert ab.compareAndSet(false, true);
// 更改后的值为true
assert ab.get();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1558761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

node.js的错误处理

当我打开一个不存在的文件时&#xff0c;错误如下&#xff1a; 在读取文件里面写入console.log&#xff08;err&#xff09;&#xff0c;在控制台中可以看到我的错误代码类型&#xff1a;文件不存在的错误代码 ENOENT。见更多错误代码---打开node.js官方API文档Error 错误 | N…

AtCoder Beginner Contest 347 A - E

A - Divisible 大意 给定个数&#xff0c;对于其中能被整除的数&#xff0c;输出商。 思路 直接计算即可。 代码 #include<iostream> using namespace std; int main(){ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);int n, k;cin >> n >> k;while…

IDEA 详细设置

详细设置 如何打开详细配置界面 1、显示工具栏 2、选择详细配置菜单或按钮 系统设置 默认启动项目配置 启动IDEA时&#xff0c;默认自动打开上次开发的项目&#xff1f;还是自己选择&#xff1f; 如果去掉Reopen projects on startup前面的对勾&#xff0c;每次启动IDEA就会…

OSCP靶场--Access

OSCP靶场–Access 考点( 文件上传[黑名单apache.htaccess绕过] Kerberoasting SeManageVolume滥用提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.216.187 -sV -sC -Pn --min-rate 2500 -p- Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-3…

【图轮】【 最小生成树】【 并集查找】1489. 找到最小生成树里的关键边和伪关键边

本文涉及知识点 图轮 最小生成树 并集查找 关键边 1489. 找到最小生成树里的关键边和伪关键边 给你一个 n 个点的带权无向连通图&#xff0c;节点编号为 0 到 n-1 &#xff0c;同时还有一个数组 edges &#xff0c;其中 edges[i] [fromi, toi, weighti] 表示在 fromi 和 to…

【Leetcode】2580. 统计将重叠区间合并成组的方案数

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个二维整数数组 ranges &#xff0c;其中 ranges[i] [starti, endi] 表示 starti 到 endi 之间&#xff08;包括二者&#xff09;的所有整数都包含在第 i 个区间中。 你需要…

loadbalancer 引入与使用

在消费中pom中引入 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> 请求调用加 LoadBalanced 注解 进行服务调用 默认负载均衡是轮训模式 想要切换…

基于Java+SpringBoot+vue仓库管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

转移指令的原理

文章目录 转移指令的原理1. 操作符offset2. jmp指令3. 依据位移进行转移的jmp指令4. 转移的目的地址在指令中的jmp指令5. 转移地址在寄存器中的jmp指令6. 转移地址在内存中的jmp指令7. jcxz指令8. loop指令9. 根据位移进行转移的意义10. 编译器对转移位移超界的检测 转移指令的…

六种典型的商业间谍软件实例分析

近年来&#xff0c;随着数字化经济的快速发展&#xff0c;这也推动了商业间谍软件数量的急剧增长。目前&#xff0c;商业间谍软件的主要类型包括以下六种。 商业间谍软件是一种软件应用程序/脚本&#xff0c;也被称为“跟踪软件”、“监视软件”&#xff0c;主要功能包括非法数…

Kerberos 认证 javax.security.auth.logon.LoginException:拒绝链接 (Connection refused)

kerberos 服务重启之后异常 项目中用到了hive 和hdfs &#xff0c;权限认证使用了Kerberos&#xff0c;因为机房异常&#xff0c;导致了Kerberos 服务重启&#xff0c;结果发现本来运行正常的应用服务hive 和hdfs 认证失败&#xff0c;报错信息是 典型的网络连接异常 排查思路…

【C语言终章】预处理详解(下)

【C语言终章】预处理详解&#xff08;下&#xff09; 当你看到了这里时&#xff0c;首先要恭喜你&#xff01;因为这里就是C语言的最后一站了&#xff0c;你的编程大能旅途也将从此站开始&#xff0c;为坚持不懈的你鼓个掌吧&#xff01; &#x1f955;个人主页&#xff1a;开敲…

2023年蓝桥杯省赛——蜗牛

目录 题目链接&#xff1a;1.蜗牛 - 蓝桥云课 (lanqiao.cn) 思路 暴力贪心 代码实不了现 动态规划 代码实现 难点解释 总结 题目链接&#xff1a;1.蜗牛 - 蓝桥云课 (lanqiao.cn) 思路 暴力贪心 蓝桥杯反正是能暴力出来一个用例是一个&#xff0c;我真的被折磨了好久&…

10.python的字典dict(上)

10.python的字典dict(上) 什么是字典 在计算机科学中&#xff0c;字典是一种数据结构&#xff0c;用于存储键值对&#xff08;key-value pair&#xff09;的集合。每个键值对都由一个唯一的键和一个对应的值组成。字典能够快速地根据键找到对应的值&#xff0c;因此在很多编程…

探索----------------阿里云

目录 一、阿里云四大件 1、云服务器ECS 2、云数据库RDS 3、负载均衡SLB 4、对象存储OSS 5、其他的云计算产品 1&#xff09;内容分发网络CDN 2&#xff09;专有网络 VPC 二、linux发行版本 三、你平时对系统会怎么优化&#xff08;五大负载&#xff09; 1、cpu 使用率…

有什么好用的网页在线客服系统?选择指南与实用推荐

有什么好用的网页在线客服系统&#xff1f; 企业与客户之间的互动变得越来越重要。为了提高客户满意度和提升企业形象&#xff0c;许多企业开始使用网页在线客服系统。网页在线客服系统是一种实时的、便捷的沟通工具&#xff0c;可以帮助企业与客户建立更紧密的联系。 一、网…

黑马点评项目笔记 II

基于Stream的消息队列 stream是一种数据类型&#xff0c;可以实现一个功能非常完善的消息队列 key&#xff1a;队列名称 nomkstream&#xff1a;如果队列不存在是否自动创建&#xff0c;默认创建 maxlen/minid&#xff1a;设置消息队列的最大消息数量 *|ID 唯一id&#xff1a;…

C++刷题篇——07检测热点字符

一、题目 二、解题思路 1、使用map&#xff0c;key为元素&#xff0c;value为出现的次数 2、由于sort不适用于map&#xff0c;因此要将map的key、value放到vector中&#xff0c;再对vector排序 3、对map排序&#xff1a;方法1&#xff1a;使用二维数组vector<vector<>…

蓝桥杯习题

https://www.lanqiao.cn/problems/1265/learning/ 第一题---排序 给定一个长度为N的数组A&#xff0c;请你先从小到大输出它的每个元素&#xff0c;再从大到小输出他的每个元素。 输入描述&#xff1a; 第一行包含一个整数N 第二行包含N个整数a1,a2,a3,...an&#xff0c;表…

解决多模块项目报错,找不到程序包

本周&#xff0c;我遇到了一个常见的错误——“找不到程序包”。这个错误是由于模块间的依赖关系没有正确配置导致的。经过一系列的尝试和排查&#xff0c;我最终找到了解决问题的方法。下面&#xff0c;我将详细记录这次问题的处理过程&#xff0c;并总结其中的经验教训。 问…