java并发编程之美 学习笔记
AtomicLong的局限性
JUC并发包中包含有Atomic*
原子操作类,他们的原理类似,下面以AtomicLong
类似。
在Unsafe类对AtomicLong
做了比较详细的介绍,这里不再赘述。
这里只分析下这个AtomicLong
的局限性:
//java.util.concurrent.atomic.AtomicLong
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//sun.misc.Unsafe
/**
* @param var1 : AtomicLong obj
* @param var2 : offset
* @param var4 : 自增值:
* @return 原先值
*/
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
//获取原先值
var6 = this.getLongVolatile(var1, var2);
//不断尝试,直至更新成功
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
AtomicLong 进行自增操作时,会使用CAS操作去对变量的更新,如果设置失败,则循环不断继续尝试 , 直到设置成功 。当在高并发情况下对变量进行更新时,会产生大量的竞争,会产生大量的自旋尝试CAS操作,浪费CPU性能,如下图所示:
为了解决这个问题,JDK8新增了LongAdder
类。
LongAdder
LongAdder内部维护了多个Cell变量(数组,Cell[] cells
)和一个基值变量(base
),每个Cell里面有一个初始值为0的long型变量,在高并发情况下,由AtomicLong
争夺一个原子变量,变为争夺Cell数组的元素
,在同等并发量下,争夺会大大的减少。
另外,在一个线程争夺某一个Cell元素(如cells[i]
)失败了,它并不是一直在该元素上自旋CAS重试,而是尝试在其他Cell(如cells[k]
)上尝试,这大大增加了CAS操作成功的可能性。
最后,在获取它的值时,会把所有Cell变量的value之和 + base
返回。
Cell cells[]
,Cell数组的伪共享问题?
@sun.misc.Contended static final class Cell {}
, Cell使用@Contended 注解,防止多个数组元素共享一个cpu缓存行.
类图
Striped64类
LongAdder集成自Striped64
,
//java.util.concurrent.atomic.Striped64
abstract class Striped64 extends Number {
transient volatile Cell[] cells;
//基准值
transient volatile long base;
//cellsBusy用来实现自旋锁,状态值只有 0和 1
//当cells创建或者扩容时,加锁
transient volatile int cellsBusy;
}
Cell类
Cell 为 Striped64的一个内部类:
//java.util.concurrent.atomic.Striped64.Cell
//@Contended避免伪共享问题
@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);
}
private static final sun.misc.Unsafe UNSAFE;
//value字段的内存偏移量
private static final long valueOffset;
}
LongAdder
public class LongAdder extends Striped64 implements Serializable {
//求和
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
//在计算过程中,有可能celL状态发生了变化,而这里并没有对cells加锁
//所以sum()不是非常的精确
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
//统计所有cell的值
sum += a.value;
}
}
return sum;
}
public long longValue() {
return sum();
}
//重置
public void reset() {
Cell[] as = cells; Cell a;
//base设置为0
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
//遍历cell,设为0
a.value = 0L;
}
}
}
}
这里需要注意的是:sum()
并不是十分精确,已在代码中增加注释。
下面重点分析add()
方法。
- add()
//LongAdder
//给变量+x
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) { // (1)
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 || // (2)
(a = as[getProbe() & m]) == null || // (3)
!(uncontended = a.cas(v = a.value, v + x))) // (4)
longAccumulate(x, null, uncontended); // (5)
}
}
//Striped64
/**
* 将基准值base ,由cmp更新为val
* @param cmp 基准值(原先值)
* @param val 目标值
* @return
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
(1)
: 当cells为空时,执行casBase()
: 类似AtomicLong的操作;
当cells不为空,或者casBase()执行失败了,才会进行后续的操作:
(2),(3)
: 决定当前线程使用cells中的哪一个Cell元素。- 如果当前线程映射Cell元素存在,则执行
(4)
- 如果当前线程映射Cell元素存在,则执行
(4)
: CAS操作更新Cell的value值;- 当as 为空,或者当前线程未映射到Cell元素或者映射的Cell元素cas操作失败,则执行
(5)
:longAccumulate()
方法用来对Cells初始化和扩容;
LongAccumulator
LongAccumulator
比LongAdder
更为强大,它的构造函数中提供了一个LongBinaryOperator
的双目运算接口
;
LongBinaryOperator
@FunctionalInterface
public interface LongBinaryOperator {
//根据两个数计算一个值
long applyAsLong(long left, long right);
}
LongAccumulator
public class LongAccumulator extends Striped64 implements Serializable {
private final LongBinaryOperator function;
//初始值
private final long identity;
public LongAccumulator(LongBinaryOperator accumulatorFunction,
long identity) {
this.function = accumulatorFunction;
base = this.identity = identity;
}
public void accumulate(long x) {
Cell[] as; long b, v, r; int m; Cell a;
//通过LongBinaryOperator.applyAsLong() 来计算结果
if ((as = cells) != null ||
(r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended =
(r = function.applyAsLong(v = a.value, x)) == v ||
a.cas(v, r)))
longAccumulate(x, function, uncontended);
}
}
public long get() {
Cell[] as = cells; Cell a;
long result = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
//通过function 计算结果
result = function.applyAsLong(result, a.value);
}
}
return result;
}
}
例
public class LongBinaryOperatorTest {
static Integer[] array1 = new Integer[]{0, 1, 2, 3, 4, 0, 5, 6, 0, 56, 0};
//累加器
static LongAccumulator longAccumulator = new LongAccumulator((left,right) -> left+right,0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < array1.length; i++) {
if(array1[i] == 0){
longAccumulator.accumulate(1);
}
}
});
thread1.start();
thread1.join();
System.out.println("0:" +longAccumulator.get());
}
}