LongAdder类

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)
  • (4): CAS操作更新Cell的value值;
  • 当as 为空,或者当前线程未映射到Cell元素或者映射的Cell元素cas操作失败,则执行(5): longAccumulate()方法用来对Cells初始化和扩容;

LongAccumulator

LongAccumulatorLongAdder更为强大,它的构造函数中提供了一个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());
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值