上期咱们介绍了Java8中新的时间日期API,本期咱们介绍Java8中原子性操做
LongAdder
。java
根据百度百科的定义:算法
"原子操做(atomic operation)是不须要synchronized",这是Java多线程编程的老生常谈了。所谓原子操做是指不会被线程调度机制打断的操做;这种操做一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另外一个线程)。编程
在单线程的环境中,使用Long,若是对于多线程的环境,若是使用Long的话,须要加上synchronized
关键字,从Java5开始,JDK提供了AtomicLong
类,AtomicLong是一个提供原子操做的Long类,经过线程安全的方式操做加减,AtomicLong提供原子操做来进行Long的使用,所以十分适合高并发状况下的使用。安全
public class AtomicLongFeature {
private static final int NUM_INC = 1_000_000;
private static AtomicLong atomicLong = new AtomicLong(0);
private static void update() {
atomicLong.set(0);
ExecutorService executorService = Executors.newFixedThreadPool(5);
IntStream.range(0, NUM_INC).forEach(i -> {
Runnable task = () -> atomicLong.updateAndGet(n -> n + 2);
executorService.submit(task);
});
stop(executorService);
System.out.println(atomicLong.get());
}
private static void stop(ExecutorService executorService) {
try {
executorService.shutdown();
executorService.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (!executorService.isTerminated()) {
System.out.println("kill tasks");
}
executorService.shutdownNow();
}
}
public static void main(String[] args) {
update();
}
}
复制代码
输出: 2000000微信
为何AtomicInteger
能支持高并发呢?看下AtomicLong
的updateAndGet
方法:多线程
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
复制代码
缘由是每次updateAndGet
时都会调用compareAndSet
方法。并发
AtomicLong是在使用非阻塞算法实现并发控制,在一些高并发程序中很是适合,但并不能每一种场景都适合,不一样场景要使用使用不一样的数值类。app
AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减小的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。那么LongAdder又是使用到了什么原理?难道有比cas更加快速的方式?高并发
public class LongAdderFeature {
private static final int NUM_INC = 1_000_000;
private static LongAdder longAdder = new LongAdder();
private static void update() {
ExecutorService executorService = Executors.newFixedThreadPool(5);
IntStream.range(0, NUM_INC).forEach(i -> {
Runnable task = () -> longAdder.add(2);
executorService.submit(task);
});
stop(executorService);
System.out.println(longAdder.sum());
}
private static void stop(ExecutorService executorService) {
try {
executorService.shutdown();
executorService.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (!executorService.isTerminated()) {
System.out.println("kill tasks");
}
executorService.shutdownNow();
}
}
public static void main(String[] args) {
update();
}
}
复制代码
输出: 2000000性能
咱们来看下LongAdder的add方法:
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
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);
}
}
复制代码
咱们能够看到一个Cell的类,那这个类是用来干什么的呢?
@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 mechanics
private 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);
}
}
}
复制代码
咱们能够看到Cell类的内部是一个volatile的变量,而后更改这个变量惟一的方式经过cas。咱们能够猜想到LongAdder的高明之处可能在于将以前单个节点的并发分散到各个节点的,这样从而提升在高并发时候的效率。
LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候经过对base的直接更新能够很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候经过分散提升了性能。
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;
}
复制代码
当计数的时候,将base和各个cell元素里面的值进行叠加,从而获得计算总数的目的。这里的问题是在计数的同时若是修改cell元素,有可能致使计数的结果不许确,因此缺点是LongAdder在统计的时候若是有并发更新,可能致使统计的数据有偏差。