【JUC】原子操作类及LongAddr源码分析

文章目录

    • 1. 十八罗汉
    • 2. 原子类再分类
      • 2.1 基本类型原子类
      • 2.2 数组类型原子类
      • 2.3 引用类型原子类
      • 2.4 对象的属性修改原子类
      • 2.5 原子操作增强类
    • 3. 代码演示及性能比较:
    • 4. LongAddr原理
    • 5. LongAddr源码分析
      • 5.1 add()
      • 5.2 longAccumulate()
      • 5.3 sum()
    • 6. 小总结
      • 6.1 AtomicLong
      • 6.2 LongAdder

1. 十八罗汉

底层使用Unsafe类的CAS方法,而无需使用synchronized等重量锁使操作变得线程安全

  1. AtomicBoolean
  2. AtomicInteger
  3. AtomicIntegerArray
  4. AtomicIntegerFieldUpdater
  5. AtomicLong
  6. AtomicLongArray
  7. AtomicLongFieldUpdater
  8. AtomicMarkableReference
  9. AtomicReference
  10. AtomicReferenceArray
  11. AtomicReferenceFieldUpdater
  12. AtomicStampedReference
  13. DoubleAccumulator
  14. DoubleAdder
  15. LongAccumulator
  16. LongAdder
  17. Striped64,LongAdder是Striped64的子类
  18. Number,Striped64,AtomicInteger等是Number的子类

2. 原子类再分类

2.1 基本类型原子类

  1. AtomicBoolean
  2. AtomicInteger
  3. AtomicLong

基本使用

public class Temp {static int num = 0;static AtomicInteger atomicNum = new AtomicInteger();public static void main(String[] args) throws InterruptedException {int size = 50;CountDownLatch count = new CountDownLatch(size);for (int i = 0; i < size; i++) {new Thread(() -> {try {for (int j = 0; j < 1000; j++) {num++;atomicNum.getAndIncrement();}} finally {count.countDown();}}).start();}count.await();System.out.println(num);System.out.println(atomicNum.get());}
}

输出

39224
50000

2.2 数组类型原子类

如何理解?类比数组即可

  1. AtomicIntegerArray
  2. AtomicLongArray
  3. AtomicReferenceArray

2.3 引用类型原子类

  1. AtomicReference
  2. AtomicStampedReference
    • 用版本号解决CAS的ABA问题,可以统计出修改过几次
  3. AtomicMarkableReference
    • 用状态戳解决CAS的ABA问题,可以标记出是否修改过

2.4 对象的属性修改原子类

  1. AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值
  2. AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
  3. AtomicReferenceFieldUpdater:原子更新对象中引用类型字段的值

目的:以一种线程安全的方式操作非线程安全对象内的某些字段

要求:

  • 更新的对象属性必须使用public volatile修饰
  • 使用静态放法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性
/*** 需求:多线程环境下初始化资源类,要求只能初始化一次*/
public class Temp {public volatile Boolean isInit = Boolean.FALSE;private static final AtomicReferenceFieldUpdater<Temp, Boolean> ATOMIC_REFERENCE_FIELD_UPDATER =AtomicReferenceFieldUpdater.newUpdater(Temp.class, Boolean.class, "isInit");public void init(Temp temp) {if (ATOMIC_REFERENCE_FIELD_UPDATER.compareAndSet(temp, Boolean.FALSE, Boolean.TRUE)) {System.out.println(Thread.currentThread().getName()+":init start");try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {}System.out.println(Thread.currentThread().getName()+":init finished");} else {System.out.println(Thread.currentThread().getName()+":已有线程正在初始化");}}public static void main(String[] args) throws InterruptedException {int size = 5;CountDownLatch count = new CountDownLatch(size);Temp temp = new Temp();for (int i = 0; i < size; i++) {new Thread(() -> {try {temp.init(temp);} finally {count.countDown();}}).start();}count.await();}
}

2.5 原子操作增强类

  1. DoubleAccumulator:一个或多个变量共同维护使用的函数更新的运行double值
  2. DoubleAdder:一个或多个变量共同维持最初的零和double总和
  3. LongAccumulator:一个或多个变量共同维护使用的函数更新的运行long值
  4. LongAdder:一个或多个变量共同维持最初的零和long总和

volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

说明: 如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger0;count.addAndGet(1);如果是JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好( 减少乐观锁的重试次数 );同时,使用LongAdder的空间代价更大,故越是高并发越推荐使用LongAdder 。

LongAdder只能用来计算加法,且从零开始计算;

LongAccumulator提供了自定义函数的操作且可以传入初始值。

3. 代码演示及性能比较:

/*** 需求:点赞数统计,50个线程,每个点赞1千万次*/
public class Temp {long i = 0;public synchronized void synchronizedIncrement() {i++;}AtomicLong atomicLong = new AtomicLong();public void atomicLongIncrement() {atomicLong.getAndIncrement();}LongAdder longAdder = new LongAdder();public void longAdderIncrement() {longAdder.increment();}LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);public void longAccumulatorIncrement() {longAdder.increment();}public static void main(String[] args) throws InterruptedException {int threadNum = 50;int times = 10000000;process(threadNum, times, 1);process(threadNum, times, 2);process(threadNum, times, 3);process(threadNum, times, 4);}public static void process(int threadNum, int times, int type) throws InterruptedException {long startTime = System.currentTimeMillis();CountDownLatch countDownLatch = new CountDownLatch(threadNum);Temp temp = new Temp();for (int i = 0; i < threadNum; i++) {new Thread(() -> {try {for (int j = 0; j < times; j++) {switch (type) {case 1: temp.synchronizedIncrement();break;case 2: temp.atomicLongIncrement();break;case 3: temp.longAdderIncrement();break;case 4: temp.longAccumulatorIncrement();break;default: throw new RuntimeException("不存在的类型");}}} finally {countDownLatch.countDown();}}).start();}countDownLatch.await();long endTime = System.currentTimeMillis();System.out.println("costTime:" + (endTime - startTime) + " 毫秒," + typeStr(type));}public static String typeStr(int type) {switch (type) {case 1: return "synchronizedIncrement";case 2: return "atomicLongIncrement";case 3: return "longAdderIncrement";case 4: return "longAccumulatorIncrement";default: throw new RuntimeException("不存在的类型");}}
}

执行结果:

costTime:22854 毫秒,synchronizedIncrement
costTime:4888 毫秒,atomicLongIncrement
costTime:215 毫秒,longAdderIncrement
costTime:214 毫秒,longAccumulatorIncrement

4. LongAddr原理

LongAddr继承了Striped64,LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回

sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,
从而降级更新热点

Striped64比较重要的成员变量

    /** Number of CPUS, to place bound on table size *  CPU数量,即cells数组的最大长度*/static final int NCPU = Runtime.getRuntime().availableProcessors();/*** Table of cells. When non-null, size is a power of 2.* cells数组,长度为2的幂,2,4,8,16...,方便以后位运算*/transient volatile Cell[] cells;/*** Base value, used mainly when there is no contention, but also as* a fallback during table initialization races. Updated via CAS.* 基础value值,当并发较低时,只累加该值,主要用于没有竞争的情况,通过CAS更新*/transient volatile long base;/*** Spinlock (locked via CAS) used when resizing and/or creating Cells.* 创建或扩容Cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁*/transient volatile int cellsBusy;

在这里插入图片描述

5. LongAddr源码分析

小总结:LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则是采用化整为零分散热点的做法,用空间换时间用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行换作时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程换作完毕,将数组cells的所有值和base都加起来作为最终结果

5.1 add()

public void add(long x) {// as是Striped64中的cells数组属性// b是Striped64中的base属性// v是当前线程hash到Cell中存储的值// m是cells的长度-1,hash时作为掩码使用// a是当前线程hash到的CellCell[] as; long b, v; int m; Cell a;// 首次首线程(as = cells) != null一定是false,此时走casBase方法,以CAS的方式更新base值,且只有当cas失败时,才会走到if中// 		条件1: cells不为空// 		条件2: cas操作base失败,说明其他线程先一步修改了base,出现竞争if ((as = cells) != null || !casBase(b = base, b + x)) {// true无竞争,false表示竞争激烈,多个线程hash到同一个cell,可能要扩容boolean uncontended = true;// 条件1:cells为空// 条件2:应该不会出现// 条件3:当前线程所在cell为空,说明当前线程还没有更新过cell,应初始化一个cell// 条件4:更新当前线程所在cell失败,说明竞争很激烈,多个线程hash到了同一个cell,应扩容if (as == null || (m = as.length - 1) < 0 ||// getProbe()方法返回的是线程中的threadLocalRandomProbe字段// 它是通过随机数生成的一个值,对于一个确定的线程这个值是固定的(除非刻意修改它)(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x, null, uncontended);}
}

5.2 longAccumulate()

    /*** Handles cases of updates involving initialization, resizing,* creating new Cells, and/or contention. See above for* explanation. This method suffers the usual non-modularity* problems of optimistic retry code, relying on rechecked sets of* reads.** @param x the value * @param fn the update function, or null for add (this convention* avoids the need for an extra field or function in LongAdder). * @param wasUncontended false if CAS failed before call * x: 需要增加的值,一般默认都是1* fn: 默认传递的是null* wasUncontended: 竞争标识,如果是false标识有竞争。只有cells初始化之后,并且当前线程CAS修改失败才会是false*/final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {// h:存储线程的probe值int h;// 如果getProbe()方法返回0,说明随机数未初始化if ((h = getProbe()) == 0) { // 这个操作相当于给当前线程生成一个非0的hash值// 使用ThreadLocalRandom为当前线程重新计算一个hash值,强制初始化ThreadLocalRandom.current(); // force initialization// 重新获取probe值,hash值被重置就好比一个全新的线程一样,所以设置了wasUncontended竞争状态为trueh = getProbe();// 重新计算了当前线程的hash后认为此次不算是一次竞争,都未初始化,肯定还不存在竞争激烈,故设置wasUncontended竞争状态为truewasUncontended = true;}// 如果hash取模映射得到的cell单元不是null,则为true,此值也可以看做是扩容意向boolean collide = false;                // True if last slot nonemptyfor (;;) {// 自旋:按CASE 2,3,1看代码Cell[] as; Cell a; int n; long v;// CASE 1:cells已经被初始化了if ((as = cells) != null && (n = as.length) > 0) {// CASE 1.1 :当前线程hash到的cell还未初始化,则需要进行初始化处理,初始化的值为x即默认的1if ((a = as[(n - 1) & h]) == null) {if (cellsBusy == 0) {       // Try to attach new CellCell r = new Cell(x);   // Optimistically create// 双端检查并尝试抢锁if (cellsBusy == 0 && casCellsBusy()) {boolean created = false;try {               // Recheck under lockCell[] rs; int m, j;if ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {rs[j] = r;created = true;}} finally {cellsBusy = 0;}if (created)break;continue;           // Slot is now non-empty}}collide = false;}// CASE 1.2:竞争激烈,允许重新计算hash值else if (!wasUncontended)       // CAS already known to failwasUncontended = true;      // Continue after rehash// CASE 1.3:当前线程hash到已初始化的槽位,使用cas进行更新,更新成功则跳出循环else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;// CASE 1.4:如果cells的长度已经大于等于CPU核数,就不要再扩容了,继续hash;cells != as说明已有线程正在扩容else if (n >= NCPU || cells != as)collide = false;            // At max size or stale// CASE 1.5:将扩容意向置为true,如果再hash仍然不满足上诉条件便扩容else if (!collide)collide = true;// CASE 1.6:尝试扩容,将老数组复制到新数组上else if (cellsBusy == 0 && casCellsBusy()) {try {// 当前的cells数组和最先赋值的as是同一个,代表没有被其他线程扩容过if (cells == as) {      // Expand table unless staleCell[] rs = new Cell[n << 1];for (int i = 0; i < n; ++i)rs[i] = as[i];cells = rs;}} finally {cellsBusy = 0;}collide = false;continue;                   // Retry with expanded table}// 允许重置当前线程的hash值h = advanceProbe(h);}// CASE 2:cells没有加锁且没有初始化,则尝试对它进行加锁,并初始化cells数组;cellsBusy=0表示无锁状态,cellsBusy=1为持锁状态else if (cellsBusy == 0 && cells == as && casCellsBusy()) {// boolean init = false;try {                           // Initialize tableif (cells == as) {// 与条件中cells == as形成双重检查Cell[] rs = new Cell[2];rs[h & 1] = new Cell(x); // 按照当前线程hash到数组中的位置并创建其对应的Cellcells = rs;init = true;}} finally {cellsBusy = 0;}if (init)break;}// CASE 3:cells正在进行初始化,则尝试直接在基数base上进行累加操作;兜底操作,当新线程进来,而数组正在初始化时,则用base进行累加else if (casBase(v = base, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;                          // Fall back on using base}}

5.3 sum()

    /*** Returns the current sum.  The returned value is <em>NOT</em> an* atomic snapshot; invocation in the absence of concurrent* updates returns an accurate result, but concurrent updates that* occur while the sum is being calculated might not be* incorporated.** @return the sum*/public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;}

sum执行时,并没有限制对base和cells的更新。所以LongAdder不是强一致性的,它是最终一致性的

6. 小总结

6.1 AtomicLong

原理:CAS+自旋

场景:低并发下的全局计算,AtomicLong能保证并发情况下计数的准确性,内部使用CAS来解决并发安全问题

缺陷:高并发后性能急剧下降,N个线程CAS操作修改线程的值,每次只有一个成功,其它N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,很浪费CPU资源

6.2 LongAdder

原理:CAS+Base+Cell数组分散,空间换时间并分散了热点数据

场景:高并发下的全局计算

缺陷:sum求和时还有计算线程修改结果的话,最后的结果不够准确

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

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

相关文章

C++算法前缀和的应用:分割数组的最大值的原理、源码及测试用例

分割数组的最大值 相关知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例&#xff1a;付视频课程 二分 过些天整理基础知识 题目 给定一个非负整数数组 nums 和一个整数 m &#xff0c;你需要将这个数组分成 m 个非空的连续子数组。 设计一个算法…

VsCode通过Git History插件查看某个页面的版本修改记录

首先需要安装插件Git History 方式一&#xff1a;通过 点击File History 查看某个文件变更&#xff1b;即通过commit的提交记录去查看某个文件的修改 方式二&#xff1a;通过点击选择toggle File Blame 查看当前页面每一行所有提交修改记录

基于nodejs+vue小型企业银行账目管理系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

毫米波雷达模块技术革新:在自动驾驶汽车中的前沿应用

随着自动驾驶技术的快速发展&#xff0c;毫米波雷达模块的技术革新成为推动这一领域的关键因素之一。本文将深入研究毫米波雷达模块技术的最新进展&#xff0c;并探讨其在自动驾驶汽车中的前沿应用。 毫米波雷达模块的基本原理 解释毫米波雷达模块的基本工作原理&#xff0c;强…

springboot+html实现简单注册登录

前端&#xff1a; register.html <!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>register</title><link rel"stylesheet" type"text/css" href"/css/style.css&…

Unity Ugui 顶点颜色赋值

一、效果图 如下图&#xff1a;图片和文字的颜色都可以渐变&#xff0c;透明度也可以渐变。 原理分析&#xff1a; 不管是图片Image或是文本Text&#xff0c;它们都是网络Mesh来渲染网格是由很多三角形组成&#xff0c;那么我们根据坐标修改三角形的颜色即可实现。 工程源码…

嵌入式面试经典30问

嵌入式面试经典30问 很多同学说很害怕面试&#xff0c;看见面试官会露怯&#xff0c;怕自己的知识体系不完整&#xff0c;怕面试官考的问题回答不上了&#xff0c;所以今天为大家准备了嵌入式工程师面试经常遇到的30个经典问题&#xff0c;希望可以帮助大家提前准备&#xff0…

使用navicat查看类型颜色

问题描述&#xff1a; 最近遇到一个mongodb的数据问题。 在date日期数据中&#xff0c;混入了string类型的数据&#xff0c;导致查询视图报错&#xff1a; $add only supports numeric or date types解决办法&#xff1a; 使用类型颜色工具。 找到在last_modified_date字段中…

iOS 中,isa 指针

每个对象都有 isa 指针&#xff0c;指向对象所属的类。例如类 NSString 其实是类对象。 类对象产生于编译期&#xff0c;单例。 类对象有 isa 指针指向对应元类&#xff0c;元类&#xff08;metaclass&#xff09;中保存了创建类对象以及类方法所需的所有信息。 struct objc_…

Qt事件传播机制 day8

Qt事件传播机制 day8 事件的接受和忽略 当空间忽略事件时&#xff0c;事件会继续往上传播&#xff0c;这里的传播指传播给父组件QEvent有accept()函数与ignore()函数 accept()&#xff1a;本组件处理该事件&#xff0c;这个事件就不会被继续传播给其父组件ignore()&#xff1…

C/C++文件操作(细节满满,part2)

该文章上一篇&#xff1a;C/C文件操作&#xff08;细节满满&#xff0c;part1&#xff09;_仍有未知等待探索的博客-CSDN博客 个人主页&#xff1a;仍有未知等待探索_C语言疑难,数据结构,小项目-CSDN博客 专题分栏&#xff1a;C语言疑难_仍有未知等待探索的博客-CSDN博客 目录 …

儿童口腔卫生:建立健康微笑的基石

引言 儿童口腔卫生是维护健康的关键部分&#xff0c;它不仅影响口腔健康&#xff0c;还对全身健康产生必然影响。本文将探讨一些儿童口腔卫生的重要性以及儿童的关键注意事项&#xff0c;以帮助家长和监护人确保儿童拥有健康的口腔。 第一部分&#xff1a;儿童口腔卫生的重要性…

【LeetCode热题100】--31.下一个排列

31.下一个排列 思路&#xff1a; 方法&#xff1a;两遍扫描 注意到下一个排列总是比当前排列要大&#xff0c;除非该排列已经是最大的排列。我们希望找到一种方法&#xff0c;能够找到一个大于当前序列的新序列&#xff0c;且变大的幅度尽可能小。具体地&#xff1a; 我们需要…

GEO生信数据挖掘(九)WGCNA分析

第六节&#xff0c;我们使用结核病基因数据&#xff0c;做了一个数据预处理的实操案例。例子中结核类型&#xff0c;包括结核&#xff0c;潜隐进展&#xff0c;对照和潜隐&#xff0c;四个类别。第七节延续上个数据&#xff0c;进行了差异分析。 第八节对差异基因进行富集分析。…

智慧渔业方案:AI渔政视频智能监管平台助力水域禁渔执法

一、方案背景 国内有很多水库及河流设立了禁渔期&#xff0c;加强渔政执法监管对保障国家渔业权益、维护渔业生产秩序、保护渔民群众生命财产安全、推进水域生态文明建设具有重要意义。目前&#xff0c;部分地区的监管手段信息化水平低下&#xff0c;存在人员少、职责多、任务…

JavaScript反爬虫技巧详细攻略

目录 1、动态生成内容 2、使用JavaScript混淆和压缩 3、使用CORS策略 4、检测用户行为 5、利用用户代理标识符 6、图片替代和隐藏字段 7、使用反爬虫服务 在当今的web开发中&#xff0c;JavaScript已经成为了一个不可或缺的部分。然而&#xff0c;这也引发了一个问题&am…

老师如何发布考试成绩?

成绩查询页面是什么&#xff1f;如何用各种代码、Excel来实现让学生自助查询成绩&#xff1f; 作为老师&#xff0c;发布考试成绩是教学过程中的一个重要环节。传统的做法是&#xff0c;老师手动计算每个学生的分数&#xff0c;然后将成绩单打印出来并逐个发放给学生。这种方式…

MyBatisPlus(二十)防全表更新与删除

说明 针对 update 和 delete 语句&#xff0c;阻止恶意的全表更新和全表删除。 实现方式 配置BlockAttackInnerInterceptor拦截器 代码 package com.example.core.config;import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.p…

JVM第七讲:JVM 基础 - Java 内存模型详解

JVM 基础 - Java 内存模型详解 本文是JVM第七讲&#xff0c;JVM 基础 - Java 内存模型详解。主要转载自 Info 上深入理解Java内存模型, 作者程晓明。这篇文章对JMM讲的很清楚了&#xff0c;大致分三部分&#xff1a;1、重排序与顺序一致性&#xff1b;2、三个同步原语&#xff…

竞赛 深度学习YOLO安检管制物品识别与检测 - python opencv

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov55 模型训练6 实现效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLO安检管制误判识别与检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&…