java并发编程十 原子累加器和Unsafe

文章目录

    • 原子累加器
      • cas 锁
      • 原理之伪共享
    • Unsafe
      • Unsafe CAS 操作

原子累加器

累加器性能比较

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 个线程,每人累加 50 万
for (int i = 0; i < 40; i++) {ts.add(new Thread(() -> {for (int j = 0; j < 500000; 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:" + (end - start)/1000_000);}

比较 AtomicLong 与 LongAdder

 for (int i = 0; i < 5; i++) {demo(() -> new LongAdder(), adder -> adder.increment());}for (int i = 0; i < 5; i++) {demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());}

输出

1000000 cost:43 
1000000 cost:9 
1000000 cost:7 
1000000 cost:7 
1000000 cost:7 
1000000 cost:31 
1000000 cost:27 
1000000 cost:28 
1000000 cost:24 
1000000 cost:22 

性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性
能。

  • 源码之 LongAdder
    LongAdder 是并发大师 @author Doug Lea 的作品,设计的非常精巧
    LongAdder 类有几个关键域
// 累加单元数组, 懒惰初始化
transient volatile Cell[] cells;// 基础值, 如果没有竞争, 则用 cas 累加这个域
transient volatile long base;// 在 cells 创建或扩容时, 置为 1, 表示加锁
transient volatile int cellsBusy;

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();

输出

18:27:07.198 c.Test42 [Thread-0] - begin... 
18:27:07.202 c.Test42 [Thread-0] - lock... 
18:27:07.198 c.Test42 [Thread-1] - begin... 
18:27:08.204 c.Test42 [Thread-0] - unlock... 
18:27:08.204 c.Test42 [Thread-1] - lock... 
18:27:08.204 c.Test42 [Thread-1] - unlock... 

原理之伪共享

其中 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);}// 省略不重要代码
}

得从缓存说起
缓存与内存的速度比较
在这里插入图片描述

从 cpu 到大约需要的时钟周期
寄存器1 cycle (4GHz 的 CPU 约为0.25ns)
L13~4 cycle
L210~20 cycle
L340~45 cycle
内存120~240 cycle

因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。
而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效
在这里插入图片描述

因为 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,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
    在这里插入图片描述

累加主要调用下面的方法

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;if (// as 还没有创建
as == null || (m = as.length - 1) < 0 ||// 当前线程对应的 cell 还没有(a 
= as[getProbe() & m]) == null ||// cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )!(uncontended = a.cas(v = a.value, v + x))) {// 进入 cell 数组创建、cell 创建的流程longAccumulate(x, null, uncontended);}}}

add 流程图
在这里插入图片描述

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {int h;// 当前线程还没有对应的 cell, 需要随机生成一个 h 值用来将当前线程绑定到 cellif ((h = getProbe()) == 0) {// 初始化 probeThreadLocalRandom.current();// h 对应新的 probe 值, 用来对应 cellh = getProbe();wasUncontended = true;}// collide 为 true 表示需要扩容
boolean collide = false;                
for (;;) {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 循环}// 有竞争, 改变线程对应的 cell 来重试 caselse if (!wasUncontended)wasUncontended = true;// cas 尝试累加, fn 配合 LongAccumulator 不为 null, 配合 LongAdder 为 nullelse 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;// 确保 collide 为 false 进入此分支, 就不会进入下面的 else if 进行扩容了
else if (!collide)collide = true;// 加锁
else if (cellsBusy == 0 && casCellsBusy()) {// 加锁成功, 扩容
continue;}// 改变线程对应的 cellh = advanceProbe(h);}// 还没有 cells, 尝试给 cellsBusy 加锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {// 加锁成功, 初始化 cells, 最开始长度为 2, 并填充一个 cell// 成功则 break;}// 上两种情况失败, 尝试给 base 累加
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))break;}}

longAccumulate 流程图
在这里插入图片描述

在这里插入图片描述

每个线程刚进入 longAccumulate 时,会尝试对应一个 cell 对象(找到一个坑位)
在这里插入图片描述

获取最终结果通过 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;}

Unsafe

概述
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得

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

Unsafe CAS 操作

@Dataclass Student {volatile int id;volatile String name;}
 Unsafe unsafe = UnsafeAccessor.getUnsafe();Field id = Student.class.getDeclaredField("id");Field name = Student.class.getDeclaredField("name");// 获得成员变量的偏移量
long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);Student student = new Student();// 使用 cas 方法替换成员变量的值
UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20);  // 返回 trueUnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 trueSystem.out.println(student);

输出

Student(id=20, name=张三) 

使用自定义的 AtomicData 实现之前线程安全的原子整数 Account 实现

class AtomicData {private volatile int data;static final Unsafe unsafe;static final long DATA_OFFSET;static {unsafe = UnsafeAccessor.getUnsafe();try {// data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性
DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));} 
catch (NoSuchFieldException e) {throw new Error(e);}}public AtomicData(int data) {this.data = data;}public void decrease(int amount) {int oldValue;while(true) {// 获取共享变量旧值,可以在这一行加入断点,修改 data 调试来加深理解
oldValue = data;// cas 尝试修改 data 为 旧值 + amount,如果期间旧值被别的线程改了,返回 falseif (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) {return;}}}public int getData() {return data;}}

Account 实现

Account.demo(new Account() {AtomicData atomicData = new AtomicData(10000);@Overridepublic Integer getBalance() {return atomicData.getData();}@Overridepublic void withdraw(Integer amount) {atomicData.decrease(amount);}});

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

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

相关文章

mysql select 返回列_SQL / mysql – selectdistinct / UNIQUE,但返回所有列?

你正在寻找一个组&#xff1a;select * from table group by field1偶尔可以写一个独特的声明&#xff1a;select distinct on field1 * from table然而&#xff0c;在大多数平台上&#xff0c;上述两者都不会起作用&#xff0c;因为其他列上的行为是未指定的。 (MySQL中的第一…

【渝粤题库】陕西师范大学300018 世界史(下)

《世界史&#xff08;下&#xff09;》作业 一、名词解释 1、三权分立 2、考迪罗 3、斯大林格勒会战 4、马歇尔计划 5、匈牙利事件 6、美第奇家族 7、明治维新 8、人文主义 9、古巴导弹危机 10、奴隶贸易 11、七年战争 12、大西洋宪章 13、重商主义 14、文官制度 15、《解放宣言…

【渝粤题库】陕西师范大学600002 物理化学(下) 作业(专升本)

《物理化学&#xff08;下&#xff09;》作业 一.选择题 1.已知298 K时&#xff0c;(NH4)2SO4.NaOH.Na2SO4的 分别为3.064 10-2. 2.451 10-2.2.598 10-2 Sm2 mol-1&#xff0c;则NH4OH的为 ( ) A. 1.474 10-2 B. 2.684 10-2 C. 2.949 10-2 D. 5.428 10-2 2.质量摩尔浓度…

java中的规范是什么意思_Java中的异常规范有什么好处?

我从C来到Java.在Java和C中,我们都可以指定异常.看起来像这样&#xff1a;void function_name() throw(Exception){...if (error){throw Exception("Error");}...}据我所知,在C中编写异常规范被认为是一种不好的做法.与C不同,在Java中,我们必须这样做.所以,我的问题是…

jpa中::::_项目学生:JPA标准查询

jpa中::::这是Project Student的一部分。 其他职位包括带有Jersey的Webservice Client&#xff0c;带有Jersey的 Webservice Server &#xff0c; 业务层 &#xff0c; 具有Spring Data的持久性 &#xff0c;分片集成测试数据和Webservice Integration 。 我们已经介绍了CRUD的…

【渝粤题库】陕西师范大学800011 专题地图制图

《专题地图制图》作业 一、名词解释 1、专题地图 2、范围法 3、绝对比率符号 4、电子地图 5、普通地图 6、质底法 7、分级统计图法 8、条件比率符号 9、视觉重力 10、定位图表法 11、连续比率符号 12、互补色 二、填空 1、专题地图按照内容的概括程度可以分为 、 、 。 2、专题…

国家开放大学2021春1098中学数学教学研究题目

教育 教育 试卷代号&#xff1a;1098 2021年春季学期期末统一考试 中学数学教学研究 试题 2021年7月 一、填空题&#xff08;本题共20分&#xff0c;每个空2分&#xff09; 1&#xff0e;布卢姆把认知领域的目标分为 、 、 、________4个等级目标。 2&#xff0e;中学数学教学…

java 注解scheduler_使用spring的@Scheduled注解执行定时任务,启动项目不输出警告

在applicationContext.xml中添加&#xff1a;xmlns:task"http://www.springframework.org/schema/task"xsi:schemaLocation"http://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task-4.0.xsd">java代码&…

具有ELK的APIGEE API网关日志管理(Elastic Search,Logstash和Kibana)

在本文中&#xff0c;我们将看到如何使用 Elastic Search &#xff0c; Logstash 和 Kibana 管理APIGEE API网关生成的日志 。 下图突出显示了日志数据如何流经ELK堆栈以进行数据可视化和监视。 作为API网关的一部分&#xff0c;我们将使用MessageLogging策略在代理流&#xf…

【渝粤题库】陕西师范大学209010 现代教育战略 作业 (专升本)

《现代教育战略》作业 一、辨析题 1.战术是战略的具体表现形式&#xff0c;二者是一般和特殊的关系。 2.政治品德素质是一个人对国家、民族的政治意识、立场&#xff0c;是一个人的道德好感&#xff0c;是不可教的。 3.创新的新就一般意义的新事物。 4.能力就是一个所拥有的知识…

【渝粤教育】电大中专混凝土结构题库作业 题库

1.题结构试验时&#xff0c;试件的就位型式最符合实际受力状态而应优先采用的是() A.反位试验 B.正位试验 C.卧位试验 D.异位试验 正确 正确答案&#xff1a;左边查询 学生答案&#xff1a;B 2.非破损检测技术可应用于混凝土、钢材和砖石砌体等各种材料组成的结构构件的结构试验…

java中PL层_安装pljava - RuralHunter的个人空间 - OSCHINA - 中文开源技术交流社区

pljava是pgsql跟java的桥接&#xff0c;安装以后就可以在pgsql里面调用java了。这里记录一下我在ubuntu server下安装的过程1. 下载源码编译很简单&#xff0c;下载&#xff0c;解压&#xff0c;设置一下JAVA_HOME(如果没设的话)&#xff0c;然后make2. 把生成的build目录里面的…

【渝粤题库】广东开放大学 民事诉讼法学 形成性考核

选择题 题目&#xff1a;在仲裁裁决具有可撤销的法定理由时&#xff0c;仲裁当事人可以向法院申请撤销该仲裁裁决&#xff0c;法院认为当事人的申请具有法定撤销理由的&#xff0c;可以&#xff08; &#xff09;仲裁裁决。 答案&#xff1a; A、调解 B、裁定撤销 C、判决撤销…

java依赖注入_Java依赖注入选项

java依赖注入我想花一些时间来总结一些流行的Java依赖注入&#xff08;DI&#xff09;框架。 这是可用功能的高级概述。 首先&#xff0c;什么是依赖注入&#xff1f; “依赖注入是一种软件设计模式&#xff0c;可以删除硬编码的依赖&#xff0c;并可以在运行时或编译时更改它…

【渝粤教育】电大中专跨境电子商务理论与实务 (19)作业 题库

1.亚马逊的运营模式是M2C模式:平台招商。该说法&#xff08; &#xff09; A.错误 B.正确 错误 正确答案&#xff1a;左边查询 学生答案&#xff1a;未作答 2.B2C跨境电商或平台的代表企业有敦煌网&#xff0e;阿里巴巴国际站。该说法&#xff08; &#xff09; A.错误 B.正确 …

Java连接微软ad_Java:连接到Active Directory(AD)?

我正在尝试与AD联系。我试图用这个代码来连接&#xff0c;但它似乎并没有连接。我很抱歉不能比这更具体&#xff0c;但这只是我所知道的。什么都没发生。我已经删除了我认为是这个类的非必要部分&#xff0c;在那里处理结果&#xff0c;因为在这一点上根本没有任何结果需要处理…

java se 定时任务_Java实现定时任务的三种方法

一、Quartz的特点 按作业类的继承方式来分&#xff0c;主要有以下两种&#xff1a; 作业类继承org.springframework.scheduling.quartz.QuartzJobBean类的方式作业类不继承org.springframework.scheduling.quartz.QuartzJobBean类的方式 注&#xff1a;个人比较推崇第二种&…

Spring框架介绍

这是Spring框架和Spring核心概念的简介。 在本教程中&#xff0c;我们将介绍Spring Framework的主要优点和功能。 在随后的教程中&#xff0c;我们将学习有关Spring和Spring Boot的更多信息。 总览 我们知道&#xff0c; Spring框架是Java开发人员中最受欢迎的应用程序框架。 …

【渝粤教育】电大中专职业生涯规划 (2)_1作业 题库

1职业价值观具有明确的目的性、&#xff08;&#xff09;和坚定性的职业选择的态度和行为。 A自由性 B动机性 C自觉性 D制约性 错误 正确答案&#xff1a;左边查询 学生答案&#xff1a;A 2不属于探索价值观方法的是&#xff08;&#xff09;。 A澄清反应法 B意见表决法 C间接提…

【渝粤教育】电大中专会计电算化_1作业 题库

1.下列有关会计电算化狭义概念的说法正确的是()。 A.以会计理论为主体的电子信息技术在会计工作中的应用 B.与实现电算化有关的所有工作 C.以电子计算机为主体的电子信息技术在会计工作中的应用 D.与实现电算化有关的主要工作 错误 正确答案&#xff1a;左边查询 学生答案&…