使用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 。
相关文章:
使用JUC包的AtomicXxxFieldUpdater实现更新的原子性
写在前面 本文一起来看下使用JUC包的AtomicXxxxFieldUpdater实现更新的原子性。代码位置如下: 当前有针对int,long,ref三种类型的支持。如果你需要其他类型的支持的话,也可以照葫芦画瓢。 1:例子 1.1:普…...
vue3组件通信--props
目录 1.父传子2.子传父 最近在做项目的过程中发现,props父子通信忘的差不多了。下面写个笔记复习一下。 1.父传子 父组件(FatherComponent.vue): <script setup> import ChildComponent from "/components/ChildComp…...
leetcode-75-颜色分类
题解(方案二): 1、初始化变量n0,代表数组nums中0的个数; 2、初始化变量n1,代表数组nums中0和1的个数; 3、遍历数组nums,首先将每个元素赋值为2,然后对该元素进行判断统…...
【嵌入式原理设计】实验三:带报警功能的数字电压表设计
目录 一、实验目的 二、实验环境 三、实验内容 四、实验记录及处理 五、实验小结 六、成果文件提取链接 一、实验目的 熟悉和掌握A/D转换及4位数码管、摇杆、蜂鸣器的联合工作方式 二、实验环境 Win10ESP32实验开发板 三、实验内容 1、用摇杆传感器改变接口电压&…...
C#中的接口的使用
定义接口 public interface IMyInterface {int MyProperty { get; set; }void MyMethod(); } 实现类 internal class MyClass : IMyInterface {public int MyProperty { get; set; }public void MyMethod(){Console.WriteLine("MyMethod is called");} } 目录结构…...
记一次真实项目的性能问题诊断、优化(阿里云redis分片带宽限制问题)过程
前段时间,接到某项目的压测需求。项目所有服务及中间件(redis、kafka)、pg库全部使用的阿里云。 压测工具:jmeter(分布式部署),3组负载机(每组1台主控、10台linux 负载机) 问题现象࿱…...
LeetCode - 4. 寻找两个正序数组的中位数
. - 力扣(LeetCode) 题目 给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 示例 1: 输入:nums1 …...
算法设计与分析——动态规划
1.动态规划基础 1.1动态规划的基本思想 动态规划建立在最优原则的基础上,在每一步决策上列出可能的局部解,按某些条件舍弃不能得到最优解的局部解,通过逐层筛选减少计算量。每一步都经过筛选,以每一步的最优性来保证全局的最优性…...
【实战篇】GEO是什么?还可以定义新的数据类型吗?
背景 之前,我们学习了 Redis 的 5 大基本数据类型:String、List、Hash、Set 和 Sorted Set,它们可以满足大多数的数据存储需求,但是在面对海量数据统计时,它们的内存开销很大,而且对于一些特殊的场景&…...
SpringBoot最佳实践之 - 项目中统一记录正常和异常日志
1. 前言 此篇博客是本人在实际项目开发工作中的一些总结和感悟。是在特定需求背景下,针对项目中统一记录日志(包括正常和错误日志)需求的实现方式之一,并不是普适的记录日志的解决方案。所以阅读本篇博客的朋友,可以参考此篇博客中记录日志的…...
【Flutter】状态管理:高级状态管理 (Riverpod, BLoC)
当项目变得更加复杂时,简单的状态管理方式(如 setState() 或 Provider)可能不足以有效地处理应用中状态的变化和业务逻辑的管理。在这种情况下,高级状态管理框架,如 Riverpod 和 BLoC,可以提供更强大的工具…...
OAK相机的RGB-D彩色相机去畸变做对齐
▌低畸变标准镜头的OAK相机RGB-D对齐的方法 OAK相机内置的RGB-D管道会自动将深度图和RGB图对齐。其思想是将深度图像中的每个像素与彩色图像中对应的相应像素对齐。产生的RGB-D图像可以用于OAK内置的图像识别模型将识别到的2D物体自动映射到三维空间中去,或者产生的…...
smartctl硬盘检查工具
一、smartctl工具简介 Smartmontools是一种硬盘检测工具,通过控制和管理硬盘的SMART(Self Monitoring Analysis and Reporting Technology),自动检测分析及报告技术)技术来实现的,SMART技术可以对硬盘的磁头单元、盘片电机驱动系统、硬盘…...
清空MySQL数据表
要清空 MySQL 数据表,您可以使用 TRUNCATE 或 DELETE 命令 使用 TRUNCATE 命令 TRUNCATE 命令用于删除表中的所有数据,并重置自增 ID(如果存在): TRUNCATE TABLE table_name;将 table_name 替换为您要清空的表的名称…...
2024年妈杯MathorCup大数据竞赛A题超详细解题思路
2024年妈杯大数据竞赛初赛整体难度约为0.6个国赛。A题为台风中心路径相关问题,为评价预测问题;B题为库存和销量的预测优化问题。B题难度稍大于A题,可以根据自己队伍情况进行选择。26日早六点之前发布AB两题相关解题代码论文。 下面为大家带来…...
Kafka系列之:Kafka集群磁盘条带划分和Kafka集群磁盘扩容详细方案
Kafka系列之:Kafka集群磁盘条带划分和Kafka集群磁盘扩容详细方案 一、lsblk命令二、Kafka节点磁盘条带化方案一三、Kafka节点磁盘条带化方案二四、理解逻辑区块LE五、查看kafka节点磁盘条带划分情况六、Kafka节点磁盘扩容一、lsblk命令 lsblk命令用于列出块设备的信息,包括磁…...
【LeetCode】修炼之路-0007- Reverse Integer (整数反转)【python】
题目 Reverse Integer Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-231, 231 - 1], then return 0. Assume the environment does not allow you to store 64-b…...
【Flutter】页面布局:线性布局(Row 和 Column)
在 Flutter 中,布局(Layout)是应用开发的核心之一。通过布局组件,开发者可以定义应用中的控件如何在屏幕上排列。Row 和 Column 是 Flutter 中最常用的两种线性布局方式,用于水平和垂直排列子组件。在本教程中…...
C语言巨难题:执行操作可获得的最大总奖励 I(C语言版)
1.题目: 给你一个整数数组 rewardValues,长度为 n,代表奖励的值。 最初,你的总奖励 x 为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 : 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。如果…...
【力扣】GO解决子序列相关问题
文章目录 一、引言二、动态规划方法论深度提炼子序列问题的通用解法模式 三、通用方法论应用示例:最长递增子序列(LeetCode题目300)Go 语言代码实现 四、最长连续递增序列(LeetCode题目674)Go 语言代码实现 五、最长重…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
