使用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;
        }

        @Override
        public 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;
        }

        @Override
        public 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;
        }

        @Override
        public 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的money
            fieldUpdater.incrementAndGet(this);
        }

        public int getMoney() {
            return money;
        }

        @Override
        public 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 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

构建中小企业设备管理平台:Spring Boot应用

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Android 开发 调节声音 SeekBar自定义样式

效果图 xml布局 mipmap/seekbar图片随意一张图都可以&#xff0c;这里我的图就不贴出来了 <SeekBarandroid:id"id/seekBar"android:layout_marginLeft"8dp"android:layout_width"377dp"android:layout_height"8dp"android:layou…

沈阳乐晟睿浩科技有限公司抖音小店领域的强者

在当今数字化浪潮的推动下&#xff0c;电子商务以其便捷性、高效性和广泛的覆盖面&#xff0c;成为了推动经济发展的新引擎。而抖音小店&#xff0c;作为短视频平台上的新兴电商形态&#xff0c;更是凭借其庞大的用户基础、精准的内容推送机制以及独特的购物体验&#xff0c;迅…

方形件组批优化问题

在中国制造 2025 目标背景之下&#xff0c;发展环境保护型、资源节约型的智能制造业已成为制造行业的当务之急。为了应对客户提出的各式各样的产品需求、订单组批难且产品质量 要求高的问题&#xff0c;使用数学模型辅助企业对定制化产品进行组批优化具有重要意义。本文通 过…

2024.7最新子比主题zibll7.9.2开心版源码+授权教程

授权教程&#xff1a; 1.进入宝塔搭建一个站点 绑定 api.zibll.com 域名 并上传 index.php 文件 2.设置伪静态 3.开启SSL证书&#xff0c;找一个能用的域名证书&#xff0c;将密钥(KEY)和证书(PEM格式)复制进去即可 4.在宝塔文件地址栏中输入 /etc 找到 hosts文件并打开&a…

hcia复习篇

计算机网络&#xff1a; 云技术&#xff1a; 云储存---将数据通过计算机网络传输并储存在第三方服务器。&#xff08;百度网盘&#xff09; 云计算---分布式计算。&#xff08;即共享硬件资源&#xff09; 计算机技术&#xff1a; 文字、图片、视频等---抽象文字。 抽象语言…

【Nginx系列】499错误

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java应用程序的测试覆盖率之设计与实现(一)-- 总体设计

一、背景 作为测试,如何保证开发人员提交上来的代码都被测试覆盖到,是衡量测试质量的一个重要指标。 本系列文章将要说一说,如何搭建一套测试覆盖率的系统。 包括以下内容: jacoco agent采集执行覆盖率数据jacoco climaven集成jacoco:jacoco-maven-pluginant集成jacoco:…

Linux: network: wireshark IO图的一个问题

今天遇到一个问题&#xff0c;发现wireshark画的IO图&#xff0c;前几秒没有数据&#xff0c;但是根据Raw的pcap看&#xff0c;是有包的&#xff0c;这就迷惑了。 经同事提醒&#xff0c;这个IO在设置了多个画图filter的时候&#xff0c;可能导致开始前几秒没有输出。如下图 这…

HexForge:一款用于扩展安全汇编和十六进制视图的IDA插件

关于HexForge HexForge是一款用于扩展安全汇编和十六进制视图的IDA插件&#xff0c;在该工具的帮助下&#xff0c;广大研究人员可以方便地直接从 IDA Pro 界面数据解码、解密或执行安全数据审计任务。 功能介绍 1、从 IDA 的反汇编或十六进制视图复制原始十六进制&#xff1b;…

安康旅游指南:基于SpringBoot的网站开发实践

第一章 绪论 1.1 研究现状 时代的发展&#xff0c;我们迎来了数字化信息时代&#xff0c;它正在渐渐的改变着人们的工作、学习以及娱乐方式。计算机网络&#xff0c;Internet扮演着越来越重要的角色&#xff0c;人们已经离不开网络了&#xff0c;大量的图片、文字、视频冲击着我…

spring整合使用xml方式整合Druid数据源连接池

1.普通的JDBC数据库连接使用 DriverManager 来获取&#xff0c;每次向数据库建立连接的时候都要将 Connection加载到内存中&#xff0c;再验证用户名和密码(得花费0.05s&#xff5e;1s的时间)。需要数据库连接的时候&#xff0c;就向数据库要求 一个&#xff0c;执行完成后再断…

测试造数,excel转insert语句

目录 excel转sql的insert语句一、背景二、直接上代码 excel转sql的insert语句 一、背景 在实际测试工作中&#xff0c;需要频繁地进行测试造数并插入数据库验证&#xff0c;常规的手写sql语句过于浪费时间&#xff0c;为此简单写个脚本&#xff0c;通过excel来造数&#xff0…

用更多的钱买电脑而不是手机

如果&#xff0c;我们对自己的定义是知识工作者&#xff0c;那么在工作、学习相关的电子设备投入上&#xff0c;真的别舍不得花钱。 需要留意的是&#xff0c;手机&#xff0c;对于大部分在电脑前工作的人&#xff0c;不是工作设备。在我看来&#xff0c;每年投入到电脑的钱&…

Go语言Linux环境搭建以编写第一个Go程序

目录 文章目录 目录Go语言入门1、说明2、CentOS7安装Go3、编写第一个程序3.1、编写程序3.2、运行程序3.3、生成二进制文件4、编写第一个web程序4.1、编写代码4.2、运行程序4.3、测试访问4.4、生成二进制配置Vim-go语法高亮1)、下载和设置Vundle.vim(vim安装插件的工具)2)、…

[Linux网络编程]05-TCP状态和端口复用,shutdown函数(主动方建立/关闭连接状态,被动方建立/关闭连接状态,2MSL时长,TCP其他状态)

一.TCP状态图表示 netstat -apn | grep client 查看客户端网络连接状态 netstat -apn | grep port 查看端口的网络连接状态 二.主动方&#xff0c;被动方TCP连接状态 1. 主动发起连接请求端&#xff1a; CLOSE – 发送SYN – SEND_SYN – 接收 ACK、SYN – SEND_SYN – 发送 A…

查看Chrome安装路

谷歌Google浏览器查看安装路径&#xff0c;浏览器Google Chrome浏览器查看安装路径 chrome://version/ 来源&#xff1a;笔记云

vuex使用modules模块化

1、main.js引入 //引入vuex import store from ./store new Vue({el: #app,router,store,components: { App },template: <App/>,data:function(){return{wbWinList: [] // 定义的变量&#xff0c;全局参数}}, })2、index.js import Vue from vue; import Vuex from …

刷题 - 图论

1 | bfs/dfs | 网格染色 200. 岛屿数量 访问到马上就染色&#xff08;将visited标为 true)auto [cur_x, cur_y] que.front(); 结构化绑定&#xff08;C17&#xff09;也可以不使用 visited数组&#xff0c;直接修改原始数组时间复杂度: O(n * m)&#xff0c;最多将 visited 数…

英语语法笔记

内容源于b站英语兔 目录 一、综述 动作&#xff08;动词&#xff09;&#xff1a; 1.可以独立完成的动作&#xff1a;主语加不及物动词 2.有1个动作的承受者&#xff1a;主语单及物动词宾语 3.有2个承受者&#xff1a;主语双及物动词间接宾语直接宾语 4.只有1个动作承受…