- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
转载:jdk11源码–LongAdder源码分析原理分析
先参考使用相关的文档:
高并发中计数器的实现方式有哪些?
【java】阿里为什么推荐使用LongAdder,而不是volatile?
针对JDK中的原子类,想必大家都熟悉AtomicInteger,AtomicLong等类。他们都是采用CAS乐观锁方式来实现的。
但是这种方式是否还有继续优化的空间呢?答案是肯定的。
CAS乐观锁对临界区的数据(也就是atomicLong中的volatile long value属性)进行修改,这个属性是热点数据。并发量高的时候,会出现很多线程都轮询修改value属性的情况,CPU消耗比较高
。
大家在想一下,在秒杀,拍卖,银行转账等业务场景下,可能存在以下情况:大量客户的请求都需要修改某个银行账户的余额。有一种优化策略就是将该银行热点账户拆分为多条记录,将请求hash路由到不同的子账户中进行计算。那么上述Atomic类也可以采用该种策略:热点数据拆分。
这就是阅读源码的作用,可以学习到各种各样的优秀设计,并且可以将其应用到具体的工作之中。
编写java代码测试LongAdder:
@Test
public void test() throws InterruptedException {
LongAdder longAdder = new LongAdder();
AtomicLong aLong = new AtomicLong();
ExecutorService threadPool = Executors.newFixedThreadPool(100);
for(int i = 0; i < 1000000;i++){
threadPool.execute(() -> {
longAdder.increment();
aLong.incrementAndGet();
});
}
TimeUnit.SECONDS.sleep(2);//等待线城池执行完成
System.out.println(longAdder.longValue());
System.out.println(aLong.get());
}
注意上面线程数设置的多一点,才能造成比较激烈的竞争。
在longValue()方法中打断点看一下实际的结果分布,可以看到base中有值,并且有4个cells,每个cell都有值。将4个cell的值累加再加上base的值正好是100W,详细源码分析请继续阅读:
首先画一个AtomicLong和LongAdder的临界区数据对比图:
从上面的图中大家可以看到临界区数据的分布 ,至此可以完全理解LongAdder的优化思路了。
接下来看源码,主要关注红框内的方法
LongAdder继承自Striped64类。Striped64类中核心参数:
//CPU的数量,用于cell数组容量扩容
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* 容量大小是2的幂次方。当竞争大的时候,则会修改cell中的数值
*/
transient volatile Cell[] cells;
/**
* base基础数据,CAS竞争不大的话,则直接修改base
*/
transient volatile long base;
/**
* 是否竞争激烈
*/
transient volatile int cellsBusy;
可以看到都是volatile修饰的,都解决了JMM中的可见性的问题。
首先看一下LongAdder核心的Cell类:
/**
* Padded variant of AtomicLong supporting only raw accesses plus CAS.
*
* JVM intrinsics note: It would be possible to use a release-only
* form of CAS here, if it were provided.
*/
@jdk.internal.vm.annotation.Contended static final class Cell {
volatile long value;//Cell中存储数据的属性,volatile 修饰保证可见性
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return VALUE.compareAndSet(this, cmp, val); //CAS修改
}
final void reset() {
VALUE.setVolatile(this, 0L);
}
final void reset(long identity) {
VALUE.setVolatile(this, identity);
}
final long getAndSet(long val) {
return (long)VALUE.getAndSet(this, val);
}
// VarHandle mechanics
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(Cell.class, "value", long.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
}
@jdk.internal.vm.annotation.Contended
这个注解的意思是JVm内部进行了优化,解决了伪共享问题
。
再看一下获取数据的方法:
public long longValue() {
return sum();
}
public long sum() {
Cell[] cs = cells;//cell中的数据
long sum = base;//基本数据
if (cs != null) {
for (Cell c : cs)
if (c != null)
sum += c.value;
}
return sum;
}
可以看到很简单,将base和cells数组中的数据累加起来即可。
继续,看如何CAS递增数据,这里比较复杂一些。
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
if ((cs = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[getProbe() & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
初始化时,cells肯定为空。我们先看一下casBase(b = base, b + x)
的实现
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
return BASE.compareAndSet(this, cmp, val);
}
当CAS修改base的值失败时,则说明并发比较高,则进入到if内部代码,首先设置uncontended =true
,表明竞争激烈。
接下来需要判断4个条件:
cs == null
(m = cs.length - 1) < 0
(c = cs[getProbe() & m]) == null
!(uncontended = c.cas(v = c.value, v + x))
前两个条件判断cells数组是否为空,如果是空,则走longAccumulate
方法。
第三个条件是根据当前线程与数组进行逻辑与操作,获得的cell位置如果为空则走longAccumulate方法。
第三步的结果c是线程路由的cells数组的位置。
第四个条件是对这个cell中的value进行CAS修改,修改失败则走longAccumulate方法。
通过这几步可以知道,LongAdder是在遇到并发激烈时,将线程路由到cells数组中的某个位置对该位置的Cell的value进行cas修改。而longAccumulate则会对cells数组进行扩容等维护工作。看看longAccumulate源码:
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // 强制初始化
h = getProbe();//返回当前线程的threadLocalRandomProbe值
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
done: for (;;) {
Cell[] cs; Cell c; int n; long v;
if ((cs = cells) != null && (n = cs.length) > 0) {
if ((c = cs[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // 尝试添加新的Cell
Cell r = new Cell(x); // Optimistically create乐观创建cell
if (cellsBusy == 0 && casCellsBusy()) {
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
break done;
}
} finally {
cellsBusy = 0;
}
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (c.cas(v = c.value,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
//上面这里是对cell中的value进行cas修改
break;
else if (n >= NCPU || cells != cs)
collide = false; // 判断数组大小是否大于核数【cells数组最大不超过CPU可用核数】
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {//对cells数组进行扩容,直接扩容为2倍,下面是采用位移操作 : n << 1
if (cells == cs) // Expand table unless stale
cells = Arrays.copyOf(cs, n << 1);
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try { // Initialize table
if (cells == cs) {
Cell[] rs = new Cell[2];//初始化cells数组大小是2
rs[h & 1] = new Cell(x);
cells = rs;
break done;
}
} finally {
cellsBusy = 0;
}
}
// cas修改base变量。
else if (casBase(v = base,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break done;
}
}
该方法大概思路就是无限循环对cells数组进行操作更新。如果对应的cell为空则cas创建cell并插入,如果不为空则cas修改其value值。如果cas修改失败则扩容,但是扩容最大值是CPU核数。
ACO.Visualization项目 本项目演示蚁群算法求解旅行商问题的可视化过程,包括路径上的信息素浓度、蚁群的运动过程等。项目相关的代码:https://github.com/anycad/A
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
我需要用Sql数据库制作并包含的PHP票务系统源码用户客户端和管理员。我需要个人 CMS 的这个来源。谢谢你帮助我。 最佳答案 我在不同的情况下使用了 osticket。 这里: http://ost
我的场景:我想在日志文件中写入发生异常的部分代码(例如,发生异常的行前 5 行和行后 5 行 - 或者至少是该方法的所有代码)。 我的想法是用 C# 代码反编译 pdb 文件,并从该反编译文件中找到一
RocketMQ设定了延迟级别可以让消息延迟消费,延迟消息会使用 SCHEDULE_TOPIC_XXXX 这个主题,每个延迟等级对应一个消息队列,并且与普通消息一样,会保存每个消息队列的消费进度
先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和
此篇博客学习的api如标题,分别是: current_url 获取当前页面的url; page_source 获取当前页面的源码; title 获取当前页面的titl
? 1 2
1、前言 作为一个数据库爱好者,自己动手写过简单的sql解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>>诚然讲的非常透彻,但只能提纲挈领,不能让你
gory"> 目录 运行时信号量机制 semaphore 前言 作用是什么 几个主要的方法 如何实现
自己写的一个评论系统源码分享给大家,包括有表情,还有评论机制。用户名是随机的 针对某一篇文章进行评论 function subcomment() {
一、概述 StringBuilder是一个可变的字符串序列,这个类被设计去兼容StringBuffer类的API,但不保证线程安全性,是StringBuffer单线程情况下的一个替代实现。在可能的情
一、概述 System是用的非常多的一个final类。它不能被实例化。System类提供了标准的输入输出和错误输出流;访问外部定义的属性和环境变量;加载文件和库的方法;以及高效的拷贝数组中一部分元素
在JDK中,String的使用频率和被研究的程度都非常高,所以接下来我只说一些比较重要的内容。 一、String类的概述 String类的声明如下: public final class Str
一、概述 Class的实例代表着正在运行的Java应用程序的类和接口。枚举是一种类,而直接是一种接口。每一个数组也属于一个类,这个类b被反射为具有相同元素类型和维数的所有数组共享的类对象。八大基本树
一、概述 Compiler这个类被用于支持Java到本地代码编译器和相关服务。在设计上,这个类啥也不做,他充当JIT编译器实现的占位符。 放JVM虚拟机首次启动时,他确定系统属性java.comp
一、概述 StringBuffer是一个线程安全的、可变的字符序列,跟String类似,但它能被修改。StringBuffer在多线程环境下可以很安全地被使用,因为它的方法都是通过synchroni
一、概述 Enum是所有Jav中枚举类的基类。详细的介绍在Java语言规范中有说明。 值得注意的是,java.util.EnumSet和java.util.EnumMap是Enum的两个高效实现,
一、概述 此线程指的是执行程序中的线程。 Java虚拟机允许应用程序同时执行多个执行线程。 每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守
一、抽象类Number 类继承关系 这里面的原子类、BigDecimal后面都会详细介绍。 属性和抽象方法 二、概述 所有的属性,最小-128,最大127,SIZE和BYTES代码比
我是一名优秀的程序员,十分优秀!