Java線程的JDK8對併發的新支持

1. LongAdder

和AtomicLong相似的使用方式,可是性能比AtomicLong更好。java

LongAdder與AtomicLong都是使用了原子操做來提升性能。可是LongAdder在AtomicLong的基礎上進行了熱點分離,熱點分離相似於有鎖操做中的減少鎖粒度,將一個鎖分離成若干個鎖來提升性能。在無鎖中,也能夠用相似的方式來增長CAS的成功率,從而提升性能。編程

LongAdder原理圖:多線程

AtomicLong的實現方式是內部有個value 變量,當多線程併發自增,自減時,均經過CAS 指令從機器指令級別操做保證併發的原子性。惟一會制約AtomicLong高效的緣由是高併發,高併發意味着CAS的失敗概率更高, 重試次數更多,越多線程重試,CAS失敗概率又越高,變成惡性循環,AtomicLong效率下降。併發

而LongAdder將把一個value拆分紅若干cell,把全部cell加起來,就是value。因此對LongAdder進行加減操做,只須要對不一樣的cell來操做,不一樣的線程對不一樣的cell進行CAS操做,CAS的成功率固然高了(試想一下3+2+1=6,一個線程3+1,另外一個線程2+1,最後是8,LongAdder沒有乘法除法的API)。異步

但是在併發數不是很高的狀況,拆分紅若干的cell,還須要維護cell和求和,效率不如AtomicLong的實現。LongAdder用了巧妙的辦法來解決了這個問題。ide

初始狀況,LongAdder與AtomicLong是相同的,只有在CAS失敗時,纔會將value拆分紅cell,每失敗一次,都會增長cell的數量,這樣在低併發時,一樣高效,在高併發時,這種「自適應」的處理方式,達到必定cell數量後,CAS將不會失敗,效率大大提升。函數式編程

LongAdder是一種以空間換時間的策略。函數

2. CompletableFuture

實現CompletionStage接口(40餘個方法),大多數方法多數應用在函數式編程中。而且支持流式調用 高併發

CompletableFuture是Java 8中對Future的加強版 性能

簡單實現:

import java.util.concurrent.CompletableFuture;

public class AskThread implements Runnable {
	CompletableFuture<Integer> re = null;

	public AskThread(CompletableFuture<Integer> re) {
		this.re = re;
	}

	@Override
	public void run() {
		int myRe = 0;
		try {
			myRe = re.get() * re.get();
		} catch (Exception e) {
		}
		System.out.println(myRe);
	}

	public static void main(String[] args) throws InterruptedException {
		final CompletableFuture<Integer> future = new CompletableFuture<Integer>();
		new Thread(new AskThread(future)).start();
		// 模擬長時間的計算過程
		Thread.sleep(1000);
		// 告知完成結果
		future.complete(60);
	}
}

Future最使人詬病的就是要等待,要本身去檢查任務是否完成了,在Future中,任務完成的時間是不可控的。而CompletableFuture的最大改進在於,任務完成的時間也開放了出來。

future.complete(60);

用來設置完成時間。

CompletableFuture的異步執行:

 

public static Integer calc(Integer para) {
		try {
			// 模擬一個長時間的執行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		return para * para;
	}

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		final CompletableFuture<Integer> future = CompletableFuture
				.supplyAsync(() -> calc(50));
		System.out.println(future.get());
	}

CompletableFuture的流式調用:

 

public static Integer calc(Integer para) {
		try {
			// 模擬一個長時間的執行
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		return para * para;
	}

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		CompletableFuture<Void> fu = CompletableFuture
				.supplyAsync(() -> calc(50))
				.thenApply((i) -> Integer.toString(i))
				.thenApply((str) -> "\"" + str + "\"")
				.thenAccept(System.out::println);
		fu.get();
	}

組合多個CompletableFuture:

 

public static Integer calc(Integer para) {
		return para / 2;
	}

	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		CompletableFuture<Void> fu = CompletableFuture
				.supplyAsync(() -> calc(50))
				.thenCompose(
						(i) -> CompletableFuture.supplyAsync(() -> calc(i)))
				.thenApply((str) -> "\"" + str + "\"")
				.thenAccept(System.out::println);
		fu.get();
	}

這幾個例子更可能是側重Java8的一些新特性,這裏就簡單舉下例子來講明特性,就不深究了。 

CompletableFuture跟性能上關係不大,更多的是爲了支持函數式編程,在功能上的加強。固然開放了完成時間的設置是一大亮點。

3. StampedLock

在上一篇中剛剛提到了鎖分離,而鎖分離的重要的實現就是ReadWriteLock。而StampedLock則是ReadWriteLock的一個改進。StampedLock與ReadWriteLock的區別在於,StampedLock認爲讀不該阻塞寫,StampedLock認爲當讀寫互斥的時候,讀應該是重讀,而不是不讓寫線程寫。這樣的設計解決了讀多寫少時,使用ReadWriteLock會產生寫線程飢餓現象。

因此StampedLock是一種偏向於寫線程的改進。

StampedLock示例:

 

import java.util.concurrent.locks.StampedLock;

public class Point {
	private double x, y;
	private final StampedLock sl = new StampedLock();

	void move(double deltaX, double deltaY) { // an exclusively locked method
		long stamp = sl.writeLock();
		try {
			x += deltaX;
			y += deltaY;
		} finally {
			sl.unlockWrite(stamp);
		}
	}

	double distanceFromOrigin() { // A read-only method
		long stamp = sl.tryOptimisticRead();
		double currentX = x, currentY = y;
		if (!sl.validate(stamp)) {
			stamp = sl.readLock();
			try {
				currentX = x;
				currentY = y;
			} finally {
				sl.unlockRead(stamp);
			}
		}
		return Math.sqrt(currentX * currentX + currentY * currentY);
	}
}

上述代碼模擬了寫線程和讀線程, StampedLock根據stamp來查看是否互斥,寫一次stamp變增長某個值

 

tryOptimisticRead()

就是剛剛所說的讀寫不互斥的狀況。

每次讀線程要讀時,會先判斷

 

if (!sl.validate(stamp))

validate中會先查看是否有寫線程在寫,而後再判斷輸入的值和當前的 stamp是否相同,即判斷是否讀線程將讀到最新的數據。若是有寫線程在寫,或者 stamp數值不一樣,則返回失敗。

若是判斷失敗,固然能夠重複的嘗試去讀,在示例代碼中,並無讓其重複嘗試讀,而採用的是將樂觀鎖退化成普通的讀鎖去讀,這種狀況就是一種悲觀的讀法。

 

stamp = sl.readLock();

StampedLock的實現思想:

CLH自旋鎖:當鎖申請失敗時,不會當即將讀線程掛起,在鎖當中會維護一個等待線程隊列,全部申請鎖,可是沒有成功的線程都記錄在這個隊列中。每個節點(一個節點表明一個線程),保存一個標記位(locked),用於判斷當前線程是否已經釋放鎖。當一個線程試圖得到鎖時,取得當前等待隊列的尾部節點做爲其前序節點。並使用相似以下代碼判斷前序節點是否已經成功釋放鎖

 

while (pred.locked) {   
}

這個循環就是不斷等前面那個結點釋放鎖,這樣的自旋使得當前線程不會被操做系統掛起,從而提升了性能。

固然不會進行無休止的自旋,會在若干次自旋後掛起線程。

相關文章
相關標籤/搜索