Java8新特性系列(原子性操做)

題圖:by pixel2013 From pixabay

上期咱們介紹了Java8中新的時間日期API,本期咱們介紹Java8中原子性操做LongAdderjava

原子操做

根據百度百科的定義:算法

"原子操做(atomic operation)是不須要synchronized",這是Java多線程編程的老生常談了。所謂原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另外一個線程)。編程

AtomicLong

在單線程的環境中,使用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能支持高併發呢?看下AtomicLongupdateAndGet方法:多線程

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

LongAdder

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在統計的時候若是有併發更新,可能致使統計的數據有偏差。

微信公衆號: 碼上論劍
請關注個人我的技術微信公衆號,訂閱更多內容
相關文章
相關標籤/搜索