Java8 Striped64 和 LongAdder

转载自  Java8 Striped64 和 LongAdder

数据 STRIPING

根据维基百科的这段说明:

In computer data storage, data striping is the technique of segmenting logically sequential data, such as a file, so that consecutive segments are stored on different physical storage devices.

Striping is useful when a processing device requests data more quickly than a single storage device can provide it. By spreading segments across multiple devices which can be accessed concurrently, total data throughput is increased. It is also a useful method for balancing I/O load across an array of disks. Striping is used across disk drives in redundant array of independent disks (RAID) storage, network interface controllers, different computers in clustered file systems and grid-oriented storage, and RAM in some systems.

数据 striping 就是把逻辑上连续的数据分为多个段,使这一序列的段存储在不同的物理设备上。通过把段分散到多个设备上可以增加访问并发性,从而提升总体的吞吐量。

Striped64

JDK 8 的 java.util.concurrent.atomic 下有一个包本地的类 Striped64 ,它持有常见表示和机制用于类支持动态 striping 到 64bit 值上。

设计思路

这个类维护一个延迟初始的、原子地更新值的表,加上额外的 “base” 字段。表的大小是 2 的幂。索引使用每线程的哈希码来masked。这个的几乎所有声明都是包私有的,通过子类直接访问。

表的条目是 Cell 类,一个填充过(通过 sun.misc.Contended )的 AtomicLong 的变体,用于减少缓存竞争。填充对于多数 Atomics 是过度杀伤的,因为它们一般不规则地分布在内存里,因此彼此间不会有太多冲突。但存在于数组的原子对象将倾向于彼此相邻地放置,因此将通常共享缓存行(对性能有巨大的副作用),在没有这个防备下。

部分地,因为Cell相对比较大,我们避免创建它们直到需要时。当没有竞争时,所有的更新都作用到 base 字段。根据第一次竞争(更新 base 的 CAS 失败),表被初始化为大小 2。表的大小根据更多的竞争加倍,直到大于或等于CPU数量的最小的 2 的幂。表的槽在它们需要之前保持空。

一个单独的自旋锁(“cellsBusy”)用于初始化和resize表,还有用新的Cell填充槽。不需要阻塞锁,当锁不可得,线程尝试其他槽(或 base)。在这些重试中,会增加竞争和减少本地性,这仍然好于其他选择。

通过 ThreadLocalRandom 维护线程探针字段,作为每线程的哈希码。我们让它们为 0 来保持未初始化直到它们在槽 0 竞争。然后初始化它们为通常不会互相冲突的值。当执行更新操作时,竞争和/或表冲突通过失败了的 CAS 来指示。根据冲突,如果表的大小小于容量,它的大小加倍,除非有些线程持有了锁。如果一个哈希后的槽是空的,且锁可得,创建新的Cell。否则,如果槽存在,重试CAS。重试通过 “重散列,double hashing” 来继续,使用一个次要的哈希算法(Marsaglia XorShift)来尝试找到一个自由槽位。

表的大小是有上限的,因为,当线程数多于CPU数时,假如每个线程绑定到一个CPU上,存在一个完美的哈希函数映射线程到槽上,消除了冲突。当我们到达容量,我们随机改变碰撞线程的哈希码搜索这个映射。因为搜索是随机的,冲突只能通过CAS失败来知道,收敛convergence 是慢的,因为线程通常不会一直绑定到CPU上,可能根本不会发生。然而,尽管有这些限制,在这些案例下观察到的竞争频率显著地低。

当哈希到特定 Cell 的线程终止后,Cell 可能变为空闲的,表加倍后导致没有线程哈希到扩展的 Cell 也会出现这种情况。我们不尝试去检测或移除这些 Cell,在实例长期运行的假设下,观察到的竞争水平将重现,所以 Cell 将最终被再次需要。对于短期存活的实例,这没关系。

设计思路小结

  • striping和缓存行填充:通过把类数据 striping 为 64bit 的片段,使数据成为缓存行友好的,减少CAS竞争。
  • 分解表示:对于一个数字 5,可以分解为一序列数的和:2 + 3,这个数字加 1 也等价于它的分解序列中的任一 数字加 1:5 + 1 = 2 + (3 + 1)
  • 通过把分解序列存放在表里面,表的条目都是填充后的 Cell;限制表的大小为 2 的幂,则可以用掩码来实现索引;同时把表的大小限制为大于等于CPU数量的最小的 2 的幂。
  • 当表的条目上出现竞争时,在到达容量前表扩容一倍,通过增加条目来减少竞争。

CELL 类

Cell 类是 Striped64 的静态内部类。通过注解 @sun.misc.Contended 来自动实现缓存行填充,让Java编译器和JRE运行时来决定如何填充。本质上是一个填充了的、提供了CAS更新的volatile变量。

@sun.misc.Contended static final class Cell {volatile long value;Cell(long x) { value = x; }final boolean cas(long cmp, long val) {return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);}// Unsafe mechanicsprivate static final sun.misc.Unsafe UNSAFE;private static final long valueOffset;static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> ak = Cell.class;valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value"));} catch (Exception e) {throw new Error(e);}}
}

STRIPED64

Striped64 通过一个 Cell 数组维持了一序列分解数的表示,通过 base 字段维持数的初始值,通过 cellsBusy 字段来控制 resing 和/或 创建Cell。它还提供了对数进行累加的机制。

abstract class Striped64 extends Number {static final int NCPU = Runtime.getRuntime().availableProcessors();// 存放 Cell 的表。当不为空时大小是 2 的幂。transient volatile Cell[] cells;// base 值,在没有竞争时使用,也作为表初始化竞争时的一个后备。transient volatile long base;// 自旋锁,在 resizing 和/或 创建Cell时使用。transient volatile int cellsBusy;
}

累加机制 longAccumulate

设计思路里针对机制的实现,核心逻辑。该方法处理涉及初始化、resing、创建新cell、和/或竞争的更新。

逻辑如下:

  • if 表已初始化

    • if 映射到的槽是空的,加锁后再次判断,如果仍然是空的,初始化cell并关联到槽。
    • else if (槽不为空)在槽上之前的CAS已经失败,重试。
    • else if (槽不为空、且之前的CAS没失败,)在此槽的cell上尝试更新
    • else if 表已达到容量上限或被扩容了,重试。
    • else if 如果不存在冲突,则设置为存在冲突,重试。
    • else if 如果成功获取到锁,则扩容。
    • else 重散列,尝试其他槽。
  • else if 锁空闲且获取锁成功,初始化表

  • else if 回退 base 上更新且成功则退出
  • else 继续
final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {int h;if ((h = getProbe()) == 0) {// 未初始化的ThreadLocalRandom.current(); // 强制初始化h = getProbe();wasUncontended = true;}// 最后的槽不为空则 true,也用于控制扩容,false重试。boolean collide = false;for (;;) {Cell[] as; Cell a; int n; long v;if ((as = cells) != null && (n = as.length) > 0) {// 表已经初始化if ((a = as[(n - 1) & h]) == null) {// 线程所映射到的槽是空的。if (cellsBusy == 0) {       // 尝试关联新的Cell// 锁未被使用,乐观地创建并初始化cell。Cell r = new Cell(x);if (cellsBusy == 0 && casCellsBusy()) {// 锁仍然是空闲的、且成功获取到锁boolean created = false;try {               // 在持有锁时再次检查槽是否空闲。Cell[] rs; int m, j;if ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {// 所映射的槽仍为空rs[j] = r;          // 关联 cell 到槽created = true;}} finally {cellsBusy = 0;     // 释放锁}if (created)break;               // 成功创建cell并关联到槽,退出continue;           // 槽现在不为空了}}// 锁被占用了,重试collide = false;}// 槽被占用了else if (!wasUncontended)       // 已经知道 CAS 失败wasUncontended = true;      // 在重散列后继续// 在当前槽的cell上尝试更新else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;// 表大小达到上限或扩容了;// 表达到上限后就不会再尝试下面if的扩容了,只会重散列,尝试其他槽else if (n >= NCPU || cells != as)collide = false;            // At max size or stale//  如果不存在冲突,则设置为存在冲突else if (!collide)collide = true;// 有竞争,需要扩容else if (cellsBusy == 0 && casCellsBusy()) {// 锁空闲且成功获取到锁try {if (cells == as) {      // 距上一次检查后表没有改变,扩容:加倍Cell[] rs = new Cell[n << 1];for (int i = 0; i < n; ++i)rs[i] = as[i];cells = rs;}} finally {cellsBusy = 0;     // 释放锁}collide = false;continue;                   // 在扩容后的表上重试}// 没法获取锁,重散列,尝试其他槽h = advanceProbe(h);}else if (cellsBusy == 0 && cells == as && casCellsBusy()) {// 加锁的情况下初始化表boolean 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;     // 成功初始化,已更新,跳出循环}else if (casBase(v = base, ((fn == null) ? v + x :fn.applyAsLong(v, x))))// 表未被初始化,可能正在初始化,回退使用 base。break;                          // 回退到使用 base}
}

LongAdder

LongAdder 继承自 Striped64,它的方法只针对简单的情况:cell存在且更新无竞争,其余情况都通过 Striped64 的longAccumulate方法来完成。

public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {// cells 不为空 或在 base 上cas失败。也即出现了竞争。boolean uncontended = true;//if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))// 如果所映射的槽不为空,且成功更新则返回,否则进入复杂处理流程。longAccumulate(x, null, uncontended);}
}// 获取当前的和。base值加上每个cell的值。
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;
}


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

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

相关文章

Roslyn项目系统简介

发布15年后&#xff0c;Microsoft终于开始替换Visual Studio中基于COM的C#和Visual Basic项目系统。Microsoft谈及很多有必要放弃目前所用系统的原因&#xff1a; 原生且基于COM单线程并与UI线程绑定难以通过和&#xff08;不同用途的&#xff09;子类型类扩展到聚合之外与Visu…

fastdfs 集群 java_FastDFS集群部署(转载 写的比较好)

之前介绍过关于FastDFS单机部署&#xff0c;详见博文&#xff1a;FastDFSNginx(单点部署)事例下面来玩下FastDFS集群部署&#xff0c;实现高可用(HA)服务器规划&#xff1a;跟踪服务器1【主机】(Tracker Server)&#xff1a;192.100.139.121跟踪服务器2【备机】(Tracker Server…

简析.NET Core 以及与 .NET Framework的关系

至2002微软公司推出.NET平台已近15年&#xff0c;在互联网快速迭代的浪潮中&#xff0c;许多语言已被淘汰&#xff0c;同时也有更多新的语言涌现&#xff0c;但 .Net 依然坚挺的站在系统开发平台的一线阵营中&#xff0c;并且随着.NET Core 即将到来(2016年6月27日)的正式版&am…

Jdk1.8 JUC源码增量解析(2)-atomic-LongAdder和LongAccumulator

转载自 Jdk1.8 JUC源码增量解析(2)-atomic-LongAdder和LongAccumulator功能简介&#xff1a;LongAdder是jdk1.8提供的累加器&#xff0c;基于Striped64实现。它常用于状态采集、统计等场景。AtomicLong也可以用于这种场景&#xff0c;但在线程竞争激烈的情况下&#xff0c;Long…

mysql 密码hash算法_如何用hash创建一个mySQL用户(‘sha256’,$salt.$password)?

我肯定错过了什么.我想为select-only事务设置数据库用户帐户,但mysql不允许我在创建用户帐户时选择密码的哈希方法.这失败了&#xff1a;GRANT SELECT ON myDB.* TO selectuserlocalhostIDENTIFIED BY hash(sha256, salted-myfakelongrandompasswordstring);错误1064(42000)&am…

为什么微软逐步转变为开源公司

微软目前拥有自己的 BSD Unix 操作系统&#xff0c;支持 Ubuntu 作为 Windows 10 的一个子系统&#xff0c;最近又将 Xamarin 软件开发工具包开源&#xff0c;所有这些意味着微软已不再是比尔盖茨和史蒂夫鲍尔默的微软了。 我知道这很难令人相信&#xff0c;但微软确实正大步走…

Jdk1.8 JUC源码增量解析(1)-atomic-Striped64

转载自 Jdk1.8 JUC源码增量解析(1)-atomic-Striped64功能简介&#xff1a;Striped64是jdk1.8提供的用于支持如Long累加器&#xff0c;Double累加器这样机制的基础类。Striped64的设计核心思路就是通过内部的分散计算来避免竞争(比如多线程CAS操作时的竞争)。Striped64内部包含…

java内部类选择题_java内部类详解(附相关面试题)

说起内部类这个词&#xff0c;想必很多人都不陌生&#xff0c;但是又会觉得不熟悉。原因是平时编写代码时可能用到的场景不多&#xff0c;用得最多的是在有事件监听的情况下&#xff0c;并且即使用到也很少去总结内部类的用法。今天我们就来一探究竟。一.内部类基础在Java中&am…

开源,新的平台之战

近日&#xff0c;OpenDaylight项目的执行总监Neela Jacques在文章《开源的转变&#xff1a;一种新的平台战争》 中提到&#xff1a;开源已经成为软件公司业务战略的关键&#xff0c;是一种新的平台之战。 多年来&#xff0c;开源软件似乎处于技术产业的边缘。而如今&#xff0c…

java下载图片到手机相册_Unity保存图片到Android手机且更新相册

Android 保存图片到设备前言:在许多的应用或游戏中,大多都有保存图片或者截图等等的功能,这篇文档我们的目的是通过 Unity 保存图片,并且调用 Andorid 中的更新相册的原生方法.流程步骤:编写更新相册的 Android 原生接口 -> Unity 编写保存图片逻辑以及调用更新相册 Android…

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

转载自 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例 wait, notify 和 notifyAll&#xff0c;这些在多线程中被经常用到的保留关键字&#xff0c;在实际开发的时候很多时候却并没有被大家重视。本文对这些关键字的使用进行了描述。 在 Java 中…

.NET Core 使用Dapper 操作MySQL

.NET Core 使用Dapper 操作MySQL 数据库&#xff0c; .NET Core 使用Dapper。 目前官方没有出.NET Core MySQL 驱动&#xff0c;但是已经有第三方进行改动封装出.NET Core MySQL Connector 预览版。 Dapper 也已经出了 .NET Core 预览版。 Dapper dot net 是一个轻量型的ORM&a…

Angular 2与TypeScript概览

迄今为止&#xff0c;在创建Web应用方面&#xff0c;AngularJS是当前最为流行的JavaScript框架。如今&#xff0c;Angular 2和TypeScript通过一种非常类似于Java 8的语法&#xff0c;使真正面向对象的Web开发成为了主流。 据Google的工程主管Brad Green介绍&#xff0c;有130万…

正确使用 Volatile 变量

转载自 Java 理论与实践 - 正确使用 Volatile 变量 - volatile 变量使用指南Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”&#xff1b;与 synchronized 块相比&#xff0c;volatile 变量所需的编码较少&#xff0c;并且运行时开销也较少&#xf…

java龟兔赛跑设计思路_JAVA程序设计(09)-----面对对象设计初级应用 龟兔赛跑

1.乌龟和兔子共有属性和方法 做成父类 避免重复代码package com.lovo;/*** 类&#xff1a; 动物* author Abe* 属性&#xff1a; 名字 步距 总距离 睡觉的日子*/public class Animal {protected String name;protected int step;protected int distance;protected int sleepDay…

16年国庆假期期间兼职所悟

2016年9月25日&#xff0c;学校放假了&#xff01;&#xff01;&#xff01; 学校放假11天&#xff0c;10月7号才开学&#xff0c;除了晚上上个夜班之外别的时间都在闲着&#xff0c;这么大的自己感觉闲着真不是滋味&#xff0c;于是开始疯狂的在58上找工作&#xff0c;心里想…

python flask项目过程_Python 开发过程遇到的问题

另一方面&#xff0c;也是因为时间原因&#xff0c;没有事先系统了解 python 的具体内容&#xff0c;所以开发过程中基本都是拿 java 的东西往 python 里面套。比如&#xff1a;某个功能用 java 的 ArrayList 可以解决&#xff0c;那 python 中有没有类似的东西呢&#xff1f;j…

Java 中的双重检查(Double-Check)

转载自 Java 中的双重检查&#xff08;Double-Check&#xff09; 在 Effecitve Java 一书的第 48 条中提到了双重检查模式&#xff0c;并指出这种模式在 Java 中通常并不适用。该模式的结构如下所示&#xff1a; public Resource getResource() { if (resource null) { …

使用 Autofac 进行依赖注入

先说下为什么翻译这篇文章&#xff0c;既定的方向是架构&#xff0c;然后为了学习架构就去学习一些架构模式、设计思想。 突然有一天发现依赖注入这种技能。为了使得架构可测试、易维护、可扩展&#xff0c;需要架构设计为松耦合类型&#xff0c;简单的说也就是解耦。为了解耦前…

组合的示例代码 java_java实现Composite组合模式的实例代码

//20210121写在前面&#xff1a;刚期末考试完&#xff0c;考了面向对象&#xff0c;里边儿有23个设计模式&#xff0c;我寻思着考完挨个儿实现一下&#xff0c;本文实现组合模式组合模式核心思想类似文件夹的概念&#xff0c;构件树形结构&#xff0c;树形有叶子结点和文件夹结…