上期我們介紹了Java8中新的時間日期API窘俺,本期我們介紹Java8中原子性操作
LongAdder
。
原子操作
根據(jù)百度百科的定義:
"原子操作(atomic operation)是不需要synchronized",這是Java多線程編程的老生常談了断部。所謂原子操作是指不會被線程調(diào)度機(jī)制打斷的操作仿粹;這種操作一旦開始,就一直運行到結(jié)束崭添,中間不會有任何 context switch (切換到另一個線程)寓娩。
AtomicLong
在單線程的環(huán)境中,使用Long呼渣,如果對于多線程的環(huán)境棘伴,如果使用Long的話,需要加上synchronized
關(guān)鍵字屁置,從Java5開始焊夸,JDK提供了AtomicLong
類,AtomicLong是一個提供原子操作的Long類蓝角,通過線程安全的方式操作加減阱穗,AtomicLong提供原子操作來進(jìn)行Long的使用饭冬,因此十分適合高并發(fā)情況下的使用。
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
能支持高并發(fā)呢揪阶?看下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
時都會調(diào)用compareAndSet
方法昌抠。
AtomicLong是在使用非阻塞算法實現(xiàn)并發(fā)控制,在一些高并發(fā)程序中非常適合鲁僚,但并不能每一種場景都適合炊苫,不同場景要使用使用不同的數(shù)值類。
LongAdder
AtomicLong的原理是依靠底層的cas來保障原子性的更新數(shù)據(jù)冰沙,在要添加或者減少的時候劝评,會使用死循環(huán)不斷地cas到特定的值,從而達(dá)到更新數(shù)據(jù)的目的倦淀。那么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類的內(nèi)部是一個volatile的變量撞叽,然后更改這個變量唯一的方式通過cas姻成。我們可以猜測到LongAdder的高明之處可能在于將之前單個節(jié)點的并發(fā)分散到各個節(jié)點的,這樣從而提高在高并發(fā)時候的效率愿棋。
LongAdder在AtomicLong的基礎(chǔ)上將單點的更新壓力分散到各個節(jié)點科展,在低并發(fā)的時候通過對base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并發(fā)的時候通過分散提高了性能糠雨。
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;
}
當(dāng)計數(shù)的時候才睹,將base和各個cell元素里面的值進(jìn)行疊加,從而得到計算總數(shù)的目的甘邀。這里的問題是在計數(shù)的同時如果修改cell元素琅攘,有可能導(dǎo)致計數(shù)的結(jié)果不準(zhǔn)確,所以缺點是LongAdder在統(tǒng)計的時候如果有并發(fā)更新松邪,可能導(dǎo)致統(tǒng)計的數(shù)據(jù)有誤差坞琴。