使用JUC包的AtomicXxxFieldUpdater实现更新的原子性

写在前面

本文一起来看下使用JUC包的AtomicXxxxFieldUpdater实现更新的原子性。代码位置如下:
在这里插入图片描述
当前有针对int,long,ref三种类型的支持。如果你需要其他类型的支持的话,也可以照葫芦画瓢。

1:例子

1.1:普通方式

程序:

package x;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class NormalUpdaterTest {CountDownLatch countDownLatch = new CountDownLatch(1);public static void main(String[] args) throws InterruptedException {Account account = new Account(0);List<Thread> list = new ArrayList<>();for (int i = 0; i < 20; i++) {Thread t = new Thread(new Task(account));list.add(t);t.start();}for (Thread t : list) {t.join();}System.out.println(account.toString());}private static class Task implements Runnable {private Account account;Task(Account account) {this.account = account;}@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}account.increMoney();}}}static class Account {private volatile int money;public Account(int initial) {this.money = initial;}public void increMoney() {money++;}public int getMoney() {return money;}@Overridepublic String toString() {return "Account{" +"money=" + money +'}';}}
}

我们期望的结果是100,但结果往往是比100小的,因为整个操作过程并不是线程安全的,根本原因是这个i++它不是原子的,而是三步走,首先拿到i,接着对i+1,最后将i+1的结果写回内存,所以大概率存在盖结果情况发生,运行如下:

Account{money=91}Process finished with exit code 0

当然这个结果并不是固定不变,因为存在偶然性,但一般都是小于100的。

1.2:原子方式

package x;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;public class FieldlUpdaterTest {CountDownLatch countDownLatch = new CountDownLatch(1);public static void main(String[] args) throws InterruptedException {Account account = new Account(0);List<Thread> list = new ArrayList<>();for (int i = 0; i < 20; i++) {Thread t = new Thread(new Task(account));list.add(t);t.start();}for (Thread t : list) {t.join();}System.out.println(account.toString());}private static class Task implements Runnable {private Account account;Task(Account account) {this.account = account;}@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}account.increMoney();}}}static class Account {private volatile int money;private AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");public Account(int initial) {this.money = initial;}public void increMoney() {
//            money++;
//            fieldUpdater.incrementAndGet(money);// 以原子的方式更新this的moneyfieldUpdater.incrementAndGet(this);}public int getMoney() {return money;}@Overridepublic String toString() {return "Account{" +"money=" + money +'}';}}
}

相比于普通方式增加了AtomicIntegerFieldUpdater fieldUpdater专门用来负责更新,更新逻辑也对应的变为fieldUpdater.incrementAndGet(this);,这个时候再运行:

Account{money=100}Process finished with exit code 0

就对了,因为此时加了cas的乐观锁。

2:源码分析

首先看下代码AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");:

AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,final String fieldName,final Class<?> caller) {final Field field;final int modifiers;try {...} catch (Exception ex) {throw new RuntimeException(ex);}...// 获取要更新的类和字段的内存偏移量this.cclass = (Modifier.isProtected(modifiers) &&tclass.isAssignableFrom(caller) &&!isSamePackage(tclass, caller))? caller : tclass;this.tclass = tclass;this.offset = U.objectFieldOffset(field);
}

代码fieldUpdater.incrementAndGet(this);:

// java.util.concurrent.atomic.AtomicIntegerFieldUpdater.AtomicIntegerFieldUpdaterImpl#getAndAdd
public final int getAndAdd(T obj, int delta) {accessCheck(obj);// 基于偏移量完成更新,其中getAndAddInt是cas的return U.getAndAddInt(obj, offset, delta);
}// sun.misc.Unsafe#getAndAddIntsun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}

注意代码} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));当compare不成功的时候就会返回false,会尝试再次做+1操作,即自旋,直到成功。

3:相比于AtomicXxx的优势

更小的内存占用,为什么呢?比如如下代码:

class Account {private volatile int money;
}

如果改造为AtomicInteger的方式则为;

class Account {private volatile AtomicInteger money = new AtomicInteger(0);
}

AtomicInteger money的大小在不考虑padding的情况下大概为16字节对象头+4字节的对象内容,即20个字节,那么创建100个对象大小就是2000个字节,但如果是使用AtomicXxxFieldUpdater的代码就如下:

    private volatile int money;private static AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");
}

这里我们简单的假定AtomicIntegerFieldUpdater fieldUpdater的大小是24个字节,int money的大小是4个字节。此时我们创建100个对象,money和fieldUpdater占用的大小就是4*100+24,即424,因为fieldUpdater是静态的,所以只有独一份。
可以看到AtomicIntegerFieldUpdater的方式占用了比AtomicInteger少得多的内存,而占用更少的内存,也意味着你的程序不需要申请更多的内存,所以程序执行的速度也会更快。

写在后面

参考文章列表

jvm之对象大小分析。

多线程之JUC 。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java项目-基于springboot框架的学习选课系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

MATLAB图像重心计算

图像重心&#xff08;或质心&#xff09;计算是计算机视觉和图像处理领域 应用领域广泛&#xff1a;包括医疗,生物&#xff0c;动画&#xff0c;机器人等。 该文章通过灰度转换->二值化->质心计算 以下是代码中涉及的一些数学概念和公式&#xff1a; 灰度转换&#xff1a…

力扣困难题汇总(14道)

题4&#xff08;困难&#xff09;&#xff1a; 思路&#xff1a; 找两数组中位数&#xff0c;这个看起来简单&#xff0c;顺手反应就是数第(mn)/2个&#xff0c;这个难在要求时间复杂度为log(mn)&#xff0c;所以不能这样搞&#xff0c;我的思路是&#xff1a;每次切割长度为较…

Systemd:简介

1号进程 Systemd是linux系统的守护进程&#xff0c;它要管理正在运行的 Linux 主机的许多方面&#xff0c;包括挂载文件系统、管理硬件、处理定时器以及启动和管理生产性主机所需的系统服务。 $ ps -u -p 1 USER PID %CPU %MEM VSZ RSS TTY STAT START TI…

R语言机器学习算法实战系列(九)决策树分类算法 (Decision Trees Classifier)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍教程下载数据加载R包导入数据数据预处理数据描述数据切割调节参数构建模型模型的决策树预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve特征的重要性保存模…

N9042B UXA 信号分析仪

N9042BUXA 信号分析仪 - 2Hz到50GHz - 使用 N9042B UXA X 系列信号分析仪和各种测量应用软件&#xff0c;可以测试 5G、卫星等应用中的毫米波&#xff08;mmWave&#xff09;创新设计的真实性能。 N9042B 具有是德科技信号分析仪中较大的分析带宽和较深的动态范围&#xff0c…

【云原生】Kubernetes部署Jenkins静动Slave

Kubernetes部署Jenkins静动Slave 文章目录 Kubernetes部署Jenkins静动Slave文档介绍资源列表基础环境一、Jenkins Kubernetes清单文件二、使用静态Slave2.1、安装Kubernetes插件2.2、添加Agent2.3、使用Slave 三、使用动态Slave3.1、添加凭据3.2、配置动态Slave3.3、配置Jenkin…

基于SpringBoot+Vue+uniapp微信小程序的澡堂预订的微信小程序的详细设计和实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper+代码——加性注意力(Additive Attention)

【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper代码——加性注意力&#xff08;Additive Attention&#xff09; 【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper代码——加性注意力&#xff08;Additive Attention&#xff09; 文章目录…

kubernetes(三)

k8s之持久化存储pv&pvc 存储资源管理 在基于k8s容器云平台上&#xff0c;对存储资源的使用需求通常包括以下几方面&#xff1a; 1.应用配置文件、密钥的管理&#xff1b; 2.应用的数据持久化存储&#xff1b; 3.在不同的应用间共享数据存储&#xff1b; k8s支持Volume类…

Spring MVC文件请求处理-MultipartResolver

Spring Boot中的MultipartResolver是一个用于解析multipart/form-data类型请求的策略接口&#xff0c;通常用于文件上传。 对应后端使用MultipartFile对象接收。 RequestMapping("/upload")public String uploadFile(MultipartFile file) throws IOException {Strin…

十一、数据库配置

一、Navicat配置 这个软件需要破解 密码是&#xff1a;123456&#xff1b; 新建连接》新建数据库 创建一个表 保存出现名字设置 双击打开 把id设置为自动递增 这里就相当于每一次向数据库添加一个语句&#xff0c;会自动增长id一次 二、数据库的增删改查 1、Vs 建一个控…

磁编码器的工作原理和特点

目录 概述 1 磁编码器的构造 1.1 霍尔元件 1.2 永磁体 1.3 永磁体和霍尔元件的配置 2 磁编码器的工作原理 2.1 原理介绍 2.2 电气信号转换成角度 2.3 旋转角度传感器IC 3 磁编码器的特点和主要应用 概述 本文主要介绍磁编码器的构造原理&#xff0c;工作特性和应用特…

C/C++函数调用约定:__cdecl、__stdcall、__fastcall和__thiscall

目录 1.引言 2.常见函数调用约定 2.1.__cdecl 2.2.__stdcall 2.3.__fastcall 2.4.__thiscall 3.几种调用约定比较 4.注意事项 1.引言 在C和C编程中&#xff0c;函数调用约定&#xff08;Calling Convention&#xff09;定义了函数如何接收参数、如何返回值以及由谁来清…

【小沐学Golang】基于Go语言搭建静态文件服务器

文章目录 1、简介2、安装2.1 安装版2.2 压缩版 3、基本操作3.1 go run3.2 go build3.3 go install3.4 go env3.5 go module 4、文件服务器4.1 filebrowser4.2 gohttpserver4.3 goFile 5、FAQ5.1 go.mod 为空5.2 超时 结语 1、简介 https://golang.google.cn/ Go语言诞生于2007…

word表格跨页后自动生成的顶部横线【去除方法】

Hello World! Its been a long time. 这一年重心放在了科研、做事、追寻新的经历上&#xff0c;事有正事、琐事、幸事、哀事&#xff0c;内心与认知成长了一些&#xff0c;思想成熟了几分&#xff0c;技艺也有若干收获。不管怎样&#xff0c;来打个卡吧&#xff0c;纪念一下&…

Web前端高级工程师培训:使用 Node.js 构建一个 Web 服务端程序(3)

11、HTTP 协议 11-1、协议的定义 HTTP 是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础&#xff0c;是一种 client-server 协议&#xff0c;也就是说&#xff0c;请求通常是由像浏览器这样的接受方发起的。一个完整的Web文档通…

Tailwind Starter Kit 一款极简的前端快速启动模板

Tailwind Starter Kit 是基于TailwindCSS实现的一款开源的、使用简单的极简模板扩展。会用Tailwincss就可以快速入手使用。Tailwind Starter Kit 是免费开源的。它不会在原始的TailwindCSS框架中更改或添加任何CSS。它具有多个HTML元素&#xff0c;并附带了ReactJS、Vue和Angul…

Docker安装Mysql5.7,解决无法访问DockerHub问题

Docker安装Mysql5.7&#xff0c;解决无法访问DockerHub问题 简介 Docker Hub 无法访问&#xff0c;应用安装失败&#xff0c;镜像拉取超时的解决方案。 摘要 &#xff1a; 当 Docker Hub 无法访问时&#xff0c;可以通过配置国内镜像加速来解决应用安装失败和镜像拉取超时的…

使用爬虫爬取Python中文开发者社区基础教程的数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…