JUC——原子类

news2024/11/19 21:20:07

1.基本类型原子操作类

  • AtomicInteger:整型原子类

  • AtomicBoolean:布尔型原子类

  • AtomicLong:长整型原子类

1.1常用API

public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

2.数组类型原子操作类

  • AtomicIntegerArray:整型数组原子类

  • AtomicLongArray:长整型数组原子类

  • AtomicRefereceArray:引用类型数组原子类

2.1常用API

public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i,int newValue)//返回index=i位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

3.引用类型原子操作类

  • AtomicReference:引用类型原子类

  • AtomicStampedReference:原子更新带有版本号,可解决CAS中的ABA问题,记录的是修改过几次。

  • AtomicMarkableReference:原子更新带有标记(false/true),记录的是是否被修改过。

3.1AtomicMarkableReference的使用

public class Test4 {
    static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);

    public static void main(String[] args) {
        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t1	默认标识: false
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            markableReference.compareAndSet(100, 1000, marked, !marked); //t1进行修改 mark->true

        }, "t1").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t2	默认标识: false
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = markableReference.compareAndSet(100, 2000, marked, !marked); //因为t1已经改过了,返回false
            System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASResult:" + b); //t2	t2线程CASResult:false
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());//t2	true
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());//t2	1000

        }, "t2").start();
    }
}

4.对象的属性修改原子操作类

  • AtomicIntegerFieldUpdater:原子更新对象中int类型的属性。

  • AtomicLongFieldUpdater:原子更新对象中的Long类型的属性。

  • AtomicReferenceFieldUpdater:原子更行对象中引用类型的属性。

4.1使用原因

因为Synchronized锁的是整个对象,为了减少锁的粒度,我们使用对象属性原子操作类。

4.2使用要求

  1. 属性必须使用public volatile修饰。

  2. 需要使用newUpdater()创建更新器。

4.3AtomicReferenceFieldUpdater的使用

//只能有一个线程对其初始化
class MyVar {
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");

    public void init(MyVar myVar) {
        //比较并交换进行修改
        if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "\t" + "--------------start init ,need 2 secondes");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "--------------over init");
        } else {
            System.out.println(Thread.currentThread().getName() + "\t" + "--------------已经有线程进行初始化工作了。。。。。");
        }
    }
}

public class AtomicReferenceFieldUpdaterDemo {

    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            }, String.valueOf(i)).start();
        }
    }
}
/**
 * 1	--------------start init ,need 2 secondes
 * 5	--------------已经有线程进行初始化工作了。。。。。
 * 2	--------------已经有线程进行初始化工作了。。。。。
 * 4	--------------已经有线程进行初始化工作了。。。。。
 * 3	--------------已经有线程进行初始化工作了。。。。。
 * 1	--------------over init
 */

 

5.原子操作增强类

  • LongAdder:维护了一个初始值从0开始的long类型的数据,只能用来计算加法,相较于AtomciLong,减少了自旋的次数。

  • LongAccumulator:相较于LongAdder,提供了自定义的函数操作,更加的灵活便捷。

5.1实现计数器的各种方法比较

/**
 * 需求:50个线程,每个线程100w此,算出总点赞数来
 */
class ClickNumber {
    int number = 0;

    public synchronized void clickBySynchronized() {
        number++;
    }

    AtomicLong atomicLong = new AtomicLong(0);

    public void clickByAtomicLong() {
        atomicLong.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();

    public void clickByLongAdder() {
        longAdder.increment();
    }

    LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void clickByLongAccumulator() {
        longAccumulator.accumulate(1);
    }
}

public class AccumulatorCompareDemo {
    public static final int _1W = 10000;
    public static final int THREAD_NUMBER = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long StartTime;
        long endTime;
        CountDownLatch countDownLatch1 = new CountDownLatch(THREAD_NUMBER);
        CountDownLatch countDownLatch2 = new CountDownLatch(THREAD_NUMBER);
        CountDownLatch countDownLatch3 = new CountDownLatch(THREAD_NUMBER);
        CountDownLatch countDownLatch4 = new CountDownLatch(THREAD_NUMBER);

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickBySynchronized();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByAtomicLong();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByLongAdder();
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());

        StartTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByLongAccumulator();
                    }
                } finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());

    }
}
/**
 * ------costTime: 1313 毫秒	 clickBySynchronized: 50000000
 * ------costTime: 825 毫秒	 clickByAtomicLong: 50000000
 * ------costTime: 92 毫秒	 clickByLongAdder: 50000000
 * ------costTime: 61 毫秒	 clickByLongAccumulator: 50000000
 */

5.2LongAdder为什么快?(源码分析)

        先从架构上进行分析,LongAdder继承了Striped64,Striped64继承了Number类,主要是Striped64类起到了关键性的作用。

具体为什么快?

        AtomicLong采用的是利用CAS原则对value值进行原子操作,这样多线程环境下的自旋会导致cpu资源大量的消耗,LongAdder的改进思路就是分散热点,在并发量小的情况下,仅对base进行原子操作,在并发量大的情况下,采用了Cell槽位,槽位里面用value值进行记录,这样不同的线程会命中到不同的槽位中,各个线程只对自己槽中的那个值进行CAS操作,这样的话高并发就被分散开了,要想获取到总的值,只需统计各个槽位中的value值和base的值,但是统计的数据不会高度一致(因为当sum统计时,还能对槽位数据进行操作),只能保证最终一致。

 

  1. 如果Cells数组为空,尝试用CAS更新base,成功则退出即可。

  2. 如果Cells数组为空,但是高并发下CAS更新base失败了,则调用longAccumulate()方法创建槽位,初始创建2个槽位,再次尝试CAS更新base,如果还是不行,则走“槽位法”。

  3. 如果Cells数组非空,但是当前的线程利用getProbe()算出的hash值映射到的槽位为空,则调用longAccumlate()方法对指定位置创建槽位并添加到Cell数组中,然后重新计算hash。

  4. 如果Cells数组非空,且当前线程映射的槽位不为空,CAS更新映射到的槽位的value值,成功则返回,否则,说明槽位竞争都非常大了,调用longAccumlate()方法对槽位进行扩容。

注意:槽位的个数只能为2^n个,最大不能超过CPU的核数。

5.3AtomicLong和LongAdder小总结

        AtomicLong可允许一些性能损耗,要求高精度时可使用,保证精度,多个线程对单个热点值value进行了原子操作,从而保证精度,但对Cpu资源的损耗较大。

        LongAdder当需要在高并发场景下有较好的性能表现,且对值的实时精确度要求不高时,可以使用,LongAdder中线程拥有自己的槽,各个线程只对自己槽中得那个value值进行CAS操作,从而保证性能,但牺牲了实时的精度代价,不过一定是能保证最终一致性的。

 

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

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

相关文章

Linux shell编程学习笔记7:只读变量

在编程过程中&#xff0c;我们经常会使用到一些常量&#xff0c;也就是值不需要改变的变量&#xff0c;在许多编程语言提供了常量的定义方式&#xff0c;比如c/c的define MAXNUM 99999 或 const int a 7&#xff0c;javasccipt的const a7&#xff0c; 等等。 跟以上这些方法…

协议栈——连接服务器

如对方的ip和port配置信息&#xff0c;这里的连接是指通信前的准备工作 上一篇介绍查看套接字的命令时&#xff0c;可以看到很多信息&#xff0c;但是刚刚创建出来的套接字是什么信息都没有的&#xff0c;协议栈也因此不知道和谁通信&#xff1b; 客户端填补信息 这一步中调…

免杀对抗-DLL劫持免杀

C&Py-DLL劫持-语言-调用加载 1.使用visual studio创建项目 2.将文件名重命名为.c后缀 3.将如下加载器代码生成dll文件 加载器代码&#xff1a; #include "pch.h" #include <Windows.h> #include <stdio.h> #include <string.h>#pragma comment…

Python绘图系统26:坐标和绘图函数设置

文章目录 封装绘图函数绘图系统简单的修改源代码 Python绘图系统&#xff1a; 前置源码&#xff1a; Python打造动态绘图系统&#x1f4c8;一 三维绘图系统 &#x1f4c8;二 多图绘制系统&#x1f4c8;三 坐 标 轴 定 制&#x1f4c8;四 定制绘图风格 &#x1f4c8;五 数据生成…

CCC数字钥匙设计【NFC】 --车主配对流程Phase2

1、车主配对流程介绍 车主配对可以通过车内NFC进行&#xff0c;若支持UWB测距&#xff0c;也可以通过蓝牙/UWB进行。通过NFC进行车主配对总共有5个Phase。本文档主要对Phase2进行介绍。 1) Phase0&#xff1a;准备阶段&#xff1b; 2) Phase1&#xff1a;启动流程&#xff1…

如何生成你的打字速度证书?

趁着十一国庆之际&#xff0c;开发完成了打字侠的速度测试功能。我自己的打字速度约为56字/分钟&#xff0c;算是盲打中速度比较快的人咯。下面是我的打字荣誉证书呢。 你也想来试一试吗&#xff1f; 测试地址&#xff1a;https://hellotyping.com/speed-test

本地部署 Qwen-Agent

本地部署 Qwen-Agent 1. Qwen-Agent 概述2. Github 地址3. 创建虚拟环境4. 安装 flash-attention5. 部署 Qwen 模型服务6. 部署 Qwen-Agent7. 浏览器访问 Qwen Agent8. 安装浏览器助手 1. Qwen-Agent 概述 Qwen-Agent 是一个代码框架&#xff0c;用于发掘开源通义千问模型&…

拯救者R9000P 2023测试,从入门到发烧(多图预警)

20231003 By wdhuag 目录 前言&#xff1a; 开箱检查注意事项&#xff1a; 后期升级拆机参考&#xff1a; 机器配置&#xff1a; 总结&#xff1a; 开箱&#xff1a; BIOS画面&#xff1a; 备份原系统&#xff1a; 详细参数&#xff1a; 跑分&#xff1a; 安装Wind…

jenkins利用插件Active Choices Plug-in达到联动显示或隐藏参数,且参数值可修改

1. 添加组件 Active Choices Plug-in 如jenkins无法联网,可在以下两个地址中下载插件,然后放到/home/jenkins/.jenkins/plugin下面重启jenkins即可 Active Choices Active Choices | Jenkins plugin 2. 效果如下: sharding为空时,sharding_info和copy_info不显示 shard…

ElementPlus弹出消息提示框

request.post(api/furn/updata, this.form).then(res > {console.log(res);if (res.code200){//弹出消息框this.$message({message: 修改成功,type: success,});}else if (res.code 400) {//弹出消息框this.$message.error(修改失败);}//关闭对话框this.dialogFormVisible …

安装OpenPCDet跑通PointPillars

前言 刚开始接触OpenPCDet这个库&#xff0c;配置环境好久都不成功&#xff0c;后面看到这篇博文 【OpenPCDet】Kitti数据集下训练PointPillars并评估&可视化 给我提供了思路&#xff0c;本文主要记录一下安装过程中出现的错误 环境 ubuntu20.04 CUDA Version: 11.5 安…

语义分割 Semantic Segmentation

之前了解过语义分割的内容&#xff0c;感觉可以做好多东西&#xff0c;然后就抽空学习了一下&#xff0c;这里记录一下方便以后查阅&#xff0c;这篇文章可能也会随着学习的深入不断更新。 语义分割 Semantic Segmentation 一些基本概念几种语义分割算法Fully Convolutional Ne…

华硕X555YI, Win11下无法调节屏幕亮度

翻出一个旧电脑华硕X555YI&#xff0c;装Win11玩&#xff0c;已经估计到会有一些问题。 果然&#xff0c;装完之后&#xff0c;发现屏幕无法调节亮度。试了网上的一些方法&#xff0c;比如修改注册表等&#xff0c;无效。 估计是显卡比较老&#xff0c;哪里没兼容。然后用驱动…

iOS---生成证书文件的时候无法选择导出.p12文件

解决办法&#xff1a; 左栏有两个分类&#xff0c;一个钥匙串&#xff0c;一个是种类&#xff0c;要选择种类里面的【我的证书】或【证书】进行导出。选择【系统】找到【我的证书】这样导出不了"个人信息交换(.p12)" 正确做法是&#xff1a;选择【登录】找到【我的…

尚硅谷ES学习笔记一

文章目录 第1章 Elasticsearch概述01-开篇02-技术选型Elasticsearch 是什么 全文搜索引擎Elasticsearch 应用案例03-教学大纲04-入门-环境准备05-入门-RESTful & JSON06-入门-Postman客户端工具07-入门-倒排索引08-入门-HTTP-索引-创建09-入门-HTTP-索引-查询 & 删除10…

可观测平台如何存储时序曲线?滴滴实践全历程分享

滴滴的时序曲线量从 2017 年 到 2023 年增长了几十倍。整个过程中我们不断地调整和改进以应对这样的增长。例如时序数据库的选型从最初的 InfluxDB&#xff0c;到 RRDtool&#xff0c;又开发了内存 TSDB 分担查询压力&#xff0c;再到 2020 年开始使用 VictoriaMetrics。载体也…

2023年【高压电工】证考试及高压电工复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 高压电工证考试根据新高压电工考试大纲要求&#xff0c;安全生产模拟考试一点通将高压电工模拟考试试题进行汇编&#xff0c;组成一套高压电工全真模拟考试试题&#xff0c;学员可通过高压电工复审模拟考试全真模拟&a…

【PWN · ret2shellcode | “jmp esp“】[i春秋]ret2shellcode

当溢出长度不足时&#xff0c;如何将shellcode放入padding位值&#xff0c;并执行呢&#xff1f; 目录 前言 一、题目重述 ​编辑 二、题目分析 1.存在溢出 2.如何跳转 3.payload构想 4.Addr(jmp esp) 5.指令序列 三、exp 总 前言 回顾ret2shellcode发现还有很多基础的技巧没…

安装JDK(Java SE Development Kit)超详细教程

文章时间 &#xff1a; 2023-10-04 1. 下载地址 直接去下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ &#xff08;需要翻墙&#xff0c;不想翻墙或者不想注册oracel账号的&#xff0c;直接去我的阿里云盘&#xff09; 阿里云盘&#xff1a;http…