共享模型之无锁(三)
1.原子累加器
示例代码:
public class TestAtomicAdder {public static void main(String[] args) {for (int i = 0; i < 5; i++) {demo(() -> new AtomicLong(0),(adder) -> adder.getAndIncrement());}for (int i = 0; i < 5; i++) {demo(() -> new LongAdder(),(adder) -> adder.increment());}}/*** @param adderSupplier 提供者 无中生有 ()->结果* @param action 消费者 一个参数没结果* @param <T>*/private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {T adder = adderSupplier.get();long start = System.nanoTime();List<Thread> ts = new ArrayList<>();//4个线程,每个累加5万次,最终结果是200000for (int i = 0; i < 4; i++) {ts.add(new Thread(() -> {for (int j = 0; j < 50000; j++) {action.accept(adder);}}));}ts.forEach(t -> t.start());ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(adder + " cost(ns):" + (end - start) / 1000_000);}
}
比较 AtomicLong 与 LongAdder:

LongAdder累加器性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0累加Cell[0],而Thread-1累加Cell[1]… 最后将结果汇总.这样它们在累加时操作不同的Cell变量,因此减少了CAS重试失败,从而提高性能;
1.1.LongAdder源码分析
1>.LongAdder是并发大师Doug Lea(大哥李)的作品,设计的非常精巧;
2>.LongAdder类有几个关键域:
// 累加单元数组,懒惰初始化
// transient关键字作用在是序列化的时候保证这些域不会被序列化
transient volatile Cell[] cells;// 基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;// 在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;
3>.CAS锁
// 不要用于实践!!!
public class LockCas {private AtomicInteger state = new AtomicInteger(0);public void lock() {while (true) {if (state.compareAndSet(0, 1)) {break;}}}public void unlock() {log.debug("unlock...");state.set(0);}
}//测试代码
LockCas lock = new LockCas();
new Thread(() -> {log.debug("begin...");lock.lock();try {log.debug("lock...");sleep(1);} finally {lock.unlock();}
}).start();new Thread(() -> {log.debug("begin...");lock.lock();try {log.debug("lock...");} finally {lock.unlock();}
}).start();
4>.缓存行(hang)伪共享(一个缓存行加入了多个内存cell对象被称为伪共享)
其中Cell即为累加单元
// 防止缓存行伪共享,防止一个缓存行容纳多个内存cell对象
@sun.misc.Contended
static final class Cell {volatile long value;Cell(long x) { value = x; }// 最重要的方法, 用cas方式进行累加, prev表示旧值, next表示新值final boolean cas(long prev, long next) {return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);}// 省略不重要代码
}
1.1.1.LongAdder#add()源码
public void add(long x) {// as为累加单元数组// b为基础值// x为累加值Cell[] as; long b, v; int m; Cell a;// 进入 if 的两个条件// 1.as有值,表示已经发生过竞争, 进入if// 2.cas给base累加时失败了,表示base发生了竞争,进入ifif ((as = cells) != null || !casBase(b = base, b + x)) {// uncontended表示cell没有竞争boolean uncontended = true;// as还没有创建// 当前线程对应的cell还没有// cas给当前线程的cell累加失败uncontended=false(a为当前线程的cell)if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))// 进入cell数组创建、cell创建的流程longAccumulate(x, null, uncontended);}
}

1.1.2.Striped64#longAccumulate()源码
final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {int h;// 当前线程还没有对应的cell,需要随机生成一个h值用来将当前线程绑定到cellif ((h = getProbe()) == 0) {// 初始化probeThreadLocalRandom.current(); // force initialization// h对应新的probe值,用来对应(/占用)cell
h = getProbe();wasUncontended = true;}// collide为true表示需要扩容boolean collide = false; // True if last slot nonemptyfor (;;) {Cell[] as; Cell a; int n; long v;// 已经有了cellsif ((as = cells) != null && (n = as.length) > 0) {// 还没有cellif ((a = as[(n - 1) & h]) == null) {// 为cellsBusy加锁,创建cell,cell的初始累加值为x// 成功则 break,否则继续continue循环if (cellsBusy == 0) { // Try to attach new CellCell r = new Cell(x); // Optimistically createif (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;}// 有竞争,改变线程对应的cell来重试caselse if (!wasUncontended) // CAS already known to failwasUncontended = true; // Continue after rehash// cas尝试累加,fn配合LongAccumulator不为null,配合LongAdder为null
else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;// 如果cells长度已经超过了最大长度,或者已经扩容,改变线程对应的cell来重试caselse if (n >= NCPU || cells != as)collide = false; // At max size or stale// 确保collide为false进入此分支,就不会进入下面的else if进行扩容了
else if (!collide)collide = true;// 加锁else if (cellsBusy == 0 && casCellsBusy()) {try {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}// 改变线程对应的cell对象h = advanceProbe(h);}// 还没有cells,尝试给cellsBusy加锁else if (cellsBusy == 0 && cells == as && casCellsBusy()) {// 加锁成功,初始化cells,最开始长度为2,并填充一个cellboolean init = false;try { // Initialize tableif (cells == as) {Cell[] rs = new Cell[2];rs[h & 1] = new Cell(x);cells = rs;init = true;}} finally {cellsBusy = 0;}if (init)// 成功则break;break;}// 上两种情况失败,尝试给base累加else if (casBase(v = base, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break; // Fall back on using base}
}


每个线程刚进入longAccumulate()时,会尝试对应(/占用)一个cell对象(找到一个位置)

1.1.3.LongAdder#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)//将Cells数组中的每个元素进行累加sum += a.value;}}return sum;
}
扩展: 缓存与内存的速度比较
1>.CPU内存结构:

2>.CPU访问不同的缓存和内存耗费的时钟周期

①.CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率;
②.缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64byte(8个long);
③.缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中;
④.CPU要保证数据的一致性,如果某个CPU核心更改了数据,其它CPU核心对应的整个缓存行(64byte)必须全部失效;
⑤.由于内存Cell是数组形式,在内存中是连续存储的,一个内存Cell为24字节(16字节的对象头和8字节的value),因此缓存行可以存下2个内存Cell对象.这样问题来了:
- Core-0要修改Cell[0]
- Core-1要修改Cell[1]
无论谁修改成功,都会导致对方Core的缓存行失效,比如Core-0中Cell[0]=6000,Cell[1]=8000要累加Cell[0]=6001,Cell[1]=8000,这时会让Core-1的缓存行失效;
⑥."@sun.misc.Contended"就是用来解决这个问题的,它的原理是在使用此注解的对象或字段的前后各增加128字节大小的padding(填充内存Cell),从而让CPU将(不同的)对象预读至缓存时占用不同的缓存行,这样不会造成对方缓存行的失效;
2.Unsafe对象
2.1.概述
1> Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe对象不能直接调用,只能通过反射获得;
2>.示例代码:
public class TestUnsafeDemo1 {public static void main(String[] args) throws Exception {//通过反射获取到unsafe对象,这里的参数"theUnsafe"是固定的写法,对应着unsafe对象中的(类类型)成员变量Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");//由于unsafe对象中"theUnsafe"成员变量是"private"私有的,因此要设置访问权限theUnsafe.setAccessible(true);//通过成员变量获取到所属的unsafe对象//由于unsafe对象中"theUnsafe"成员变量是静态的,因此这里原本的对象参数就是nullUnsafe unsafe = (Unsafe) theUnsafe.get(null);System.out.println(unsafe); //sun.misc.Unsafe@7ea987ac}
}
3>.封装成工具类:
public class UnsafeAccessor {static Unsafe unsafe;static {try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}
}
2.2.Unsafe CAS操作
1>.使用Unsafe对象通过CAS机制线程安全的修改对象的成员变量:
@Slf4j
public class TestUnsafeCasDemo1 {public static void main(String[] args) throws Exception {//获取unsafe对象Unsafe unsafe = UnsafeAccessor.getUnsafe();//获取对象中某个成员变量的偏移地址long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));//执行CAS操作Teacher teacher = new Teacher();boolean b = unsafe.compareAndSwapInt(teacher, idOffset, 0, 1);boolean b1 = unsafe.compareAndSwapObject(teacher, nameOffset, null, "张三");//打印结果log.info(teacher.toString());}
}@Data
@NoArgsConstructor
class Teacher {volatile int id;volatile String name;
}//unsafe对象封装
class UnsafeAccessor {static Unsafe unsafe;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}
}

2.3.模拟实现原子整数
1>.以转账为例:
public class TestCustomAtomicIntegerDemo1 {public static void main(String[] args) {Account1.demo(new MyAtomicInteger(200));}
}//自定义的原子整数类
class MyAtomicInteger implements Account1 {private volatile int value;private static final long valueOffset;private static final Unsafe UNSAFE;static {UNSAFE = UnsafeAccessorUtil.getUnsafe();try {valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));} catch (NoSuchFieldException e) {e.printStackTrace();throw new RuntimeException();}}public MyAtomicInteger(int value) {this.value = value;}public int getValue() {return this.value;}public void decrement(int amount) {while (true) {int prev = this.value;int next = prev - amount;if (UNSAFE.compareAndSwapInt(this, valueOffset, prev, next)) {break;}}}@Overridepublic Integer getBalance() {return this.getValue();}@Overridepublic void withDraw(int amount) {this.decrement(amount);}
}//unsafe工具类
class UnsafeAccessorUtil {static Unsafe unsafe;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}
}//封装转账相关的方法
interface Account1 {//获取余额Integer getBalance();//取款void withDraw(int amount);/*** 方法内会启动20个线程,每个线程做(-10元)的操作* 如果初始余额为200那么正确的结果应当是 0*/static void demo(Account1 account) {List<Thread> ts = new ArrayList<>();long start = System.nanoTime();for (int i = 0; i < 20; i++) {ts.add(new Thread(() -> {account.withDraw(10);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(account.getBalance()+ " cost: " + (end - start) / 1000_000 + " ms");}
}

相关文章:
共享模型之无锁(三)
1.原子累加器 示例代码: public class TestAtomicAdder {public static void main(String[] args) {for (int i 0; i < 5; i) {demo(() -> new AtomicLong(0),(adder) -> adder.getAndIncrement());}for (int i 0; i < 5; i) {demo(() -> new LongAdder(),(…...
微信小程序 Springboot校运会高校运动会管理系统
3.1小程序端 小程序登录页面,用户也可以在此页面进行注册并且登录等。 登录成功后可以在我的个人中心查看自己的个人信息或者修改信息等 在广播信息中我们可以查看校运会发布的一些信息情况。 在首页我们可以看到校运会具体有什么项目运动。 在查看具体有什么活动我…...
走进独自开,带你轻松干副业
今天给大家分享一个开发者的福利平台——独自开(点击直接注册),让你在家就能解决收入问题。 文章目录一、平台介绍二、系统案例三、获取收益四、使用平台1、用户注册2、用户认证3、任务报价五、文末总结一、平台介绍 简单说明 独自开信息科技…...
SpringBoot+Vue实现师生健康信息管理系统
文末获取源码 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏…...
数据库第四章节第三次作业内容
1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号,不显示重复的部门号。 3、求出所有职工的人数。 4、列出最高工和最低工资。 5、列出职工的平均工资和总工资。 6、创建一个只有职工号、姓名和参加工作的新表,名为工作日期表…...
一篇五分生信临床模型预测文章代码复现——FIgure 9.列线图构建,ROC分析,DCA分析 (四)
之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…...
神经网络实战--使用迁移学习完成猫狗分类
前言: Hello大家好,我是Dream。 今天来学习一下如何使用基于tensorflow和keras的迁移学习完成猫狗分类,欢迎大家一起前来探讨学习~ 本文目录:一、加载数据集1.调用库函数2.加载数据集3.数据集管理二、猫狗数据集介绍1.猫狗数据集介…...
Attention机制 学习笔记
学习自https://easyai.tech/ai-definition/attention/ Attention本质 Attention(注意力)机制如果浅层的理解,跟他的名字非常匹配。他的核心逻辑就是“从关注全部到关注重点”。 比如我们人在看图片时,对图片的不同地方的注意力…...
数据类型与运算符
1.字符型作用: 字符型变量用于显示单个字符语法: char cc a ;注意1: 在显示字符型变量时,用单引号将字符括起来,不要用双引号注意2: 单引号内只能有一个字符,不可以是字符串C和C中字符型变量只占用1个字节。字符型变是并不是把字符本身放到内存中存储&am…...
算法刷题-二叉树的锯齿形层序遍历、用栈实现队列 栈设计、买卖股票的最佳时机 IV
文章目录二叉树的锯齿形层序遍历(树、广度优先搜索)用栈实现队列(栈、设计)买卖股票的最佳时机 IV(数组、动态规划)二叉树的锯齿形层序遍历(树、广度优先搜索) 给定一个二叉树&…...
华为OD机试 - 最小传递延迟(Python)| 代码编写思路+核心知识点
最小传递延迟 题目 通讯网络中有 N 个网络节点 用 1 ~ N 进行标识 网络通过一个有向无环图进行表示 其中图的边的值,表示节点之间的消息传递延迟 现给定相连节点之间的延时列表 times[i]={u,v,w} 其中 u 表示源节点,v 表示目的节点,w 表示 u 和 v 之间的消息传递延时 请计…...
集中供热调度系统天然气仪表内网仪表图像识别案例
一、项目需求 出于能耗采集与冬季集中供暖工作的节能和能耗分析需要,要采集现场的6块天然气表计,并存储进入客户的mySQL数据库中,现场采集的表计不允许接线,且网络环境为内网环境,需要采集表计数据并存入数据库&#…...
笔试题-2023-复旦微-数字IC设计【纯净题目版】
回到首页:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 推荐内容:数字IC设计学习比较实用的资料推荐 题目背景 笔试时间:2022.07.26应聘岗位:数字前端工程师笔试时长:120min笔试平台:赛码题目类型:基础题(10道)、选做题(10道)、验证题(5道)主观评价 难…...
【Linux】冯诺依曼体系结构和操作系统概念
文章目录🎪 冯诺依曼体系结构🚀1.体系概述🚀2.CPU和内存的数据交换🚀3.体系结构中数据的流动🎪 操作系统概念理解🚀1.简述🚀2.设计目的🚀3.定位🚀4.理解🚀5.管…...
HTML5之HTML基础学习笔记
列表标签 列表的应用场景 场景:在网页中按照行展示关联性的内容,如:新闻列表、排行榜、账单等特点:按照行的方式,整齐显示内容种类:无序列表、有序列表、自定义列表 这是老师PPT上的内容, 列表…...
FreeRTOS信号量 | FreeRTOS十
目录 说明: 一、信号量 1.1、信号量简介 1.2、信号量特点 二、二值信号量 2.1、二值信号量简介 2.2、获取与释放二值信号量函数 2.3、二值信号量使用过程与相关API函数 2.4、创建二值信号量函数了解 2.5、释放二值信号量了解 2.6、获取二值信号量了解 三…...
【SpringBoot】SpringBoot常用注解
一、前言首先这里说的SpringBoot常用注解是指在我们开发项目过程中,我们经常使用的注解,包含Spring、SpringBoot、SpringCloud、SpringMVC等这些框架中的注解,而不仅仅是SpringBoot中的注解。这里只是作一个注解列举,每个注解具体…...
数据一致性
目录一、AOP 动态代理切入方法(1) Aspect Oriented Programming(2) 切入点表达式二、SpringBoot 项目扫描类(1) ResourceLoader 扫描类(2) Map 的 computeIfAbsent 方法(3) 反射几个常用 api① 创建一个测试注解② 创建测试 PO 类③ 反射 api 获取指定类的指定注解信息(4) 返回…...
Docker不做虚拟化内核,对.NET有什么影响?
引子前两天刷抖音,看见了这样一个问题。问题:容器化不做虚拟内核,会有什么弊端?Java很多方法会跟CPU的核数有关,这个时候调用系统函数,读到的是宿主机信息,而不是我们限制资源的大小。思考&…...
HTML总结
CSS代码风格 空格规范: 1. 属性值前面,冒号后面,保留一个空格; 2. 选择器(标签)和大括号中间保留空格。 基本语法概述: 1.HTML标签是由尖括号包围的关键词,如<html> 2.HTM…...
音频标注:从原理到产业,AI听懂世界的“翻译官”
音频标注:从原理到产业,AI听懂世界的“翻译官” 引言 在人工智能的浪潮中,计算机视觉的“看”和自然语言处理的“读”已广为人知,而让机器学会“听”——理解并解析复杂的声音世界,正成为新的前沿。这一切的基石&…...
GitHub访问加速终极指南:5分钟告别龟速访问的完整解决方案
GitHub访问加速终极指南:5分钟告别龟速访问的完整解决方案 【免费下载链接】fetch-github-hosts 🌏 同步github的hosts工具,支持多平台的图形化和命令行,内置客户端和服务端两种模式~ | Synchronize GitHub hosts tool, support m…...
如何让扫描PDF变得可搜索?OCRmyPDF-Desktop完整解决方案
如何让扫描PDF变得可搜索?OCRmyPDF-Desktop完整解决方案 【免费下载链接】pdfocr-desktop PDF OCR Application, adds an OCR text layer to scanned PDF files, allowing them to be copied and searched. 项目地址: https://gitcode.com/gh_mirrors/oc/pdfocr-d…...
零基础养龙虾:OpenClaw部署从入门到上手,一篇讲透!
2026年,OpenClaw(昵称 “龙虾”)凭借 “能真正动手干活” 的核心能力,成为开源AI Agent领域的顶流。它不仅能像ChatGPT一样聊天,更能自主操作电脑——整理文件、控制浏览器、发送邮件、甚至调用硬件设备。因其图标酷似…...
OCLP-Mod:终极指南 - 让老旧Mac免费升级到最新macOS
OCLP-Mod:终极指南 - 让老旧Mac免费升级到最新macOS 【免费下载链接】OCLP-Mod A mod version for OCLP,with more interesting features. 项目地址: https://gitcode.com/gh_mirrors/oc/OCLP-Mod 你是否拥有一台被苹果官方"抛弃"的老旧Mac&#x…...
OpenClaw多用户方案:QwQ-32B共享环境下的权限隔离
OpenClaw多用户方案:QwQ-32B共享环境下的权限隔离 1. 为什么需要多用户方案? 去年我在家里搭建了一个OpenClaw自动化环境,原本只是个人使用。直到某天家人看到我用语音指令让AI自动整理照片、生成周报后,纷纷要求"共享&quo…...
全桥LLC变换器死区时间优化实战:从IGBT硬开通到完美ZVS的调试记录
全桥LLC变换器死区时间优化实战:从IGBT硬开通到完美ZVS的调试记录 在电力电子领域,LLC谐振变换器因其高效率、高功率密度和良好的EMI特性,已成为中高功率应用的理想选择。然而,实际调试过程中,死区时间与励磁电感的匹配…...
OpenClaw技能扩展:用QwQ-32B实现公众号自动发布
OpenClaw技能扩展:用QwQ-32B实现公众号自动发布 1. 为什么需要公众号自动化发布 作为一个技术博主,我每周都要在公众号发布2-3篇技术文章。最让我头疼的不是写作本身,而是发布前的繁琐流程:手动调整Markdown格式、生成封面图、上…...
【紧急预警】CPython 3.12升级后,3款主流内存工具失效!2024最稳选型组合(含兼容性补丁与迁移路径)
第一章:Python 内存检测工具选型的底层逻辑与演进脉络Python 内存管理机制以引用计数为核心,辅以循环垃圾回收器(GC)和内存池(pymalloc),这决定了内存问题往往隐匿于对象生命周期、引用链异常或…...
Pixel Fashion Atelier保姆级教程:从INSERT COIN按钮到像素粒子物理引擎解析
Pixel Fashion Atelier保姆级教程:从INSERT COIN按钮到像素粒子物理引擎解析 1. 像素时装锻造坊简介 像素时装锻造坊是一款融合了复古游戏美学与现代AI技术的图像生成工具。它基于Stable Diffusion和Anything-v5模型构建,专为时尚设计和像素艺术创作而…...


