文章目录

  • 1 AtomicReferencey引入
  • 2 AtomicReference的基本用法
    • 2.1 AtomicReference的构造
    • 2.2 方法

1 AtomicReferencey引入

AtomicReference类提供了对象引用的非阻塞原子性读写操作,并且提供了其他一些高级的用法

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

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

  • 个人账号被设计为不可变对象,一旦创建就无法进行修改。
  • 个人账号类只包含两个字段:账号名、现金数字。
  • 为了便于验证,我们约定个人账号的现金只能增多而不能减少。

根据前两个要求,我们简单设计一个代表个人银行账号的Java类DebitCard,该类将被设计为不可变。

public class DebitCard {/*** 账户名名*/private final String account;/*** 账户金额*/private final int amount;public DebitCard(String account, int amount) {this.account = account;this.amount = amount;}public String getAccount() {return account;}public int getAmount() {return amount;}@Overridepublic String toString() {return "DebitCard{" +"account='" + account + '\'' +", amount=" + amount +'}';}
}
  1. 多线程下增加账号金额
    假设有10个人不断地向这个银行账号里打钱,每次都存入10元,因此这个个人账号在每次被别人存入钱之后都会多10元。
import java.util.concurrent.TimeUnit;import static java.util.concurrent.ThreadLocalRandom.current;/*** @author wyaoyao* @date 2021/4/19 17:19*/
public class AtomicReferenceExample1 {/*** volatile关键字修饰,每次对DebitCard对象引用的写入操作都会被其他线程看到* 创建初始DebitCard,账号金额为0元*/static volatile DebitCard debitCard = new DebitCard("zhangsan", 0);public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread("T-" + i){@Overridepublic void run() {final DebitCard dc = debitCard;// 基于全局DebitCard的金额增加10元并且产生一个新的DebitCardDebitCard newDC = new DebitCard(dc.getAccount(),dc.getAmount() + 10);// 输出全新的DebitCardSystem.out.println(newDC);// 修改全局DebitCard对象的引用debitCard = newDC;try {TimeUnit.MILLISECONDS.sleep(current().nextInt(20));} catch (InterruptedException e) {e.printStackTrace();}}}.start();}}
}

声明了一个全局的DebitCard对象的引用,并且用volatile关键字进行了修饰,其目的主要是为了使DebitCard对象引用的变化对其他线程立即可见,在每个线程中都会基于全局的DebitCard金额创建一个新的DebitCard,并且用新的DebitCard对象引用更新全局DebitCard对象的引用。
程序输出:

DebitCard{account='zhangsan', amount=10}
DebitCard{account='zhangsan', amount=20}
DebitCard{account='zhangsan', amount=20}
DebitCard{account='zhangsan', amount=20}
DebitCard{account='zhangsan', amount=30}
DebitCard{account='zhangsan', amount=40}
DebitCard{account='zhangsan', amount=40}
DebitCard{account='zhangsan', amount=50}
DebitCard{account='zhangsan', amount=60}
DebitCard{account='zhangsan', amount=70}

分明已有3个人向这个账号存入了10元钱,为什么账号的金额却少于30元呢?因为volatile关键字不保证原子性

虽然被volatile关键字修饰的变量每次更改都可以立即被其他线程看到,但是我们针对对象引用的修改其实至少包含了如下两个步骤,获取该引用和改变该引用(每一个步骤都是原子性的操作,但组合起来就无法保证原子性了)。

  1. 多线程下加锁增加账号金额
public class AtomicReferenceExample1 {/*** volatile关键字修饰,每次对DebitCard对象引用的写入操作都会被其他线程看到* 创建初始DebitCard,账号金额为0元*/static volatile DebitCard debitCard = new DebitCard("zhangsan", 0);public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread("T-" + i){@Overridepublic void run() {synchronized (AtomicReferenceExample1.class){final DebitCard dc = debitCard;// 基于全局DebitCard的金额增加10元并且产生一个新的DebitCardDebitCard newDC = new DebitCard(dc.getAccount(),dc.getAmount() + 10);// 输出全新的DebitCardSystem.out.println(newDC);// 修改全局DebitCard对象的引用debitCard = newDC;}try {TimeUnit.MILLISECONDS.sleep(current().nextInt(20));} catch (InterruptedException e) {e.printStackTrace();}}}.start();}}
}

此时输出

DebitCard{account='zhangsan', amount=10}
DebitCard{account='zhangsan', amount=20}
DebitCard{account='zhangsan', amount=30}
DebitCard{account='zhangsan', amount=40}
DebitCard{account='zhangsan', amount=50}
DebitCard{account='zhangsan', amount=60}
DebitCard{account='zhangsan', amount=70}
DebitCard{account='zhangsan', amount=80}
DebitCard{account='zhangsan', amount=90}
DebitCard{account='zhangsan', amount=100}
  1. AtomicReference的非阻塞解决方案
    synchronized是一种阻塞式的解决方案,同一时刻只能有一个线程真正在工作,其他线程都将陷入阻塞,因此这并不是一种效率很高的解决方案,这个时候就可以利用AtomicReference的非阻塞原子性解决方案提供更加高效的方式了
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;import static java.util.concurrent.ThreadLocalRandom.current;/*** @author wyaoyao* @date 2021/4/19 17:19*/
public class AtomicReferenceExample2 {/*** 定义AtomicReference并且初始值为DebitCard("zhangsan", 0)*/private static AtomicReference<DebitCard> debitCardRef= new AtomicReference<>(new DebitCard("zhangsan", 0));public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread("T-" + i) {@Overridepublic void run() {// 获取AtomicReference的当前值final DebitCard dc = debitCardRef.get();// 基于AtomicReference的当前值创建一个新的DebitCardDebitCard newDC = new DebitCard(dc.getAccount(),dc.getAmount() + 10);// 基于CAS算法更新AtomicReference的当前值if(debitCardRef.compareAndSet(dc,newDC)){// 更新成功System.out.println(newDC);}try {TimeUnit.MILLISECONDS.sleep(current().nextInt(20));} catch (InterruptedException e) {e.printStackTrace();}}}.start();}}
}

在上面的程序代码中,我们使用了AtomicReference封装DebitCard的对象引用,每一次对AtomicReference的更新操作,我们都采用CAS这一乐观非阻塞的方式进行,因此也会存在对DebitCard对象引用更改失败的问题(更新时所持有的期望值引用有可能并不是AtomicReference所持有的当前引用,这也是第1小节中程序运行出现错误的根本原因。比如,A线程获得了DebitCard引用R1,在进行修改之前B线程已经将全局引用更新为R2,A线程仍然基于引用R1进行计算并且最终将全局引用更新为R1)。

CAS算法在此处就是要确保接下来要修改的对象引用是基于当前线程刚才获取的对象引用,否则更新将直接失败

DebitCard{account='zhangsan', amount=20}
DebitCard{account='zhangsan', amount=40}
DebitCard{account='zhangsan', amount=30}
DebitCard{account='zhangsan', amount=10}
DebitCard{account='zhangsan', amount=50}
DebitCard{account='zhangsan', amount=80}
DebitCard{account='zhangsan', amount=70}
DebitCard{account='zhangsan', amount=60}
DebitCard{account='zhangsan', amount=90}
DebitCard{account='zhangsan', amount=100}

控制台的输出显示账号的金额按照10的步长在增长,由于非阻塞的缘故,数值20的输出有可能会出现在数值10的前面,数值40的输出则出现在了数值30的前面,但这并不妨碍amount的数值是按照10的步长增长的。

AtomicReference所提供的非阻塞原子性对象引用读写解决方案,被应用在很多高并发容器中,比如ConcurrentHashMap

2 AtomicReference的基本用法

2.1 AtomicReference的构造

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

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

2.2 方法

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

原子类型:AtomicReference详解相关推荐

  1. python序列类型-python序列类型种类详解

    python序列类型包括哪三种 python序列类型包括:列表.元组.字典 列表:有序可变序列 创建:userlist = [1,2,3,4,5,6] 修改:userlist[5] = 999 添加: ...

  2. python编程序列类型_python序列类型种类详解

    python序列类型包括哪三种 python序列类型包括:列表.元组.字典 列表:有序可变序列 创建:userlist = [1,2,3,4,5,6] 修改:userlist[5] = 999 添加: ...

  3. (17)System Verilog枚举类型enum详解

    (17)System Verilog枚举类型enum详解 1.1 目录 1)目录 2)FPGA简介 3)System Verilog简介 4)System Verilog枚举类型enum详解 5)结语 ...

  4. 【Redis之ZSet类型的详解ZSet类型中常用命令的实践】

    Redis之ZSet类型的详解&ZSet类型中常用命令的实践 知识回顾: 通过对Redis中的String的命令做了充分的讲解以及实践学习 通过对Redis中String类型之Bit命令的详解 ...

  5. php 打开jnlp,PHP获取文件mimes类型步骤详解

    这次给大家带来PHP获取文件mimes类型步骤详解,PHP获取文件mimes类型的注意事项有哪些,下面就是实战案例,一起来看一下.<?php /* * Copyright 2010-2013 A ...

  6. Plotly中4种文本类型设置详解

    作者:Lemon 来源:Python数据之道 Plotly中4种文本类型设置详解 大家好,我是 Lemon . 在过去的一段时间里,我写了几篇用 Python 的交互式可视化工具 Plotly 来演示 ...

  7. U盘的FAT32/NTFS/exFAT文件系统类型区别详解

    U盘的FAT32/NTFS/exFAT文件系统类型区别详解 U盘相信大家非常熟悉了,用户在格式化U盘的时候可以选择文件系统:FAT32/NTFS/exFAT,那么这三个文件系统又有什么区别呢?下面我们 ...

  8. Java 泛型(generics)详解及代码示例、Java 类型通配符详解及代码示例

    Java 泛型(generics)详解及代码示例.Java 类型通配符详解及代码示例 - 概念 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制 ...

  9. Java进阶:AtomicReference详解

    前言 Atomic家族主要是保证多线程环境下的原子性,相比synchronized而言更加轻量级.比较常用的是AtomicInteger,作用是对Integer类型操作的封装,而AtomicRefer ...

最新文章

  1. c2 链路_POS链路不能打开的解决办法
  2. (0012) iOS 开发之MAC 终端命令学习
  3. lucene DocValues——本质是为通过docID查找某field的值
  4. java 把文件打包成zip_java 文件流的处理 文件打包成zip
  5. IP协议(RFC791)-IP包格式
  6. 微服务 mysql 连接池_【mysql】druid,连接池和微服务的问题
  7. 实现Ecshop商品跳到淘宝、京东等的购买链接
  8. 用C语言显示所有Ascll表
  9. 只要还在路上前行着的,那都是一个个闪闪发光惹人爱的人啊
  10. 用python在树莓派上编程,你可以将项目扩展到令人难以置信的规模
  11. python预处理c语言_C语言预处理器
  12. fastlane实现Android自动化打包
  13. 快速计算平方根数(约翰·卡马克)
  14. 你还在以为打马赛克就安全了吗?AI消除马赛克,GitHub开源项目上线三天收获近7000星
  15. APP全栈工程师修炼之路(二)
  16. 一款小程序软件开发报价包含哪些费用呢?
  17. Python语法基础 三
  18. 1.6 airtest控制MUMU模拟器
  19. Android10.0 Service启动源码解析
  20. java list逆序_Java的数组和list升序,降序,逆序函数Collections.sort和Arrays.sort的使用...

热门文章

  1. Python自动移动电驴下载完成的文件(未完)
  2. 在线去除 kindle 书籍 azw格式 DRM保护
  3. 怎么在中国知网(2021最新版)上查找核心期刊?
  4. 标定数据分析-DCM(.DCM)
  5. BEIT: BERT Pre-Training of Image Transformers(图像Transformer的BERT预训练)
  6. php 生成大写md5,php?md5
  7. 现绚丽的小球(js面向对象)
  8. epel mysql_RHEL/CentOS 6.x使用EPEL6与remi的yum源安装MySQL 5.5.x
  9. 代码管理平台——svn、git、github、gitlab
  10. 基于微信疫苗预约小程序毕业设计毕设作品(6)开题答辩PPT