強大的Stream並行流

劉信堅的部落格發表於2018-08-01

一 瞭解Stream

Stream API(java.util.stream.*)  Stream 是JAVA8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。使用Stream API對集合資料進行操作,就類似於使用SQL執行資料查詢一樣。也可使用StreamAPI做並行操作,總之,StreamAPI提供了一種高效且易於使用的處理資料的方式。

Stream是資料渠道,用於運算元據源(集合、陣列等)所生成的元素序列。集合講的是資料,流講的是計算。

注意:

1 . Stream自己不會儲存元素。

2 . Stream不會改變源物件。相反,他們會返回一個持有結果的新Stream

3 . Stream操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。

二 並行流與序列流

        並行流就是把一個內容分成多個資料塊,並用不同的執行緒分成多個資料塊,並用不同的執行緒分別處理每個資料塊的流。

JAVA8 中將並行進行了優化,我們可以很容易的對資料進行並行操作。Stream API 可以宣告性地通過parallel() 與sequential() 在並行流與順序流之間進行切換。其實JAVA8底層是使用JAVA7新加入的Fork/Join框架:

Fork/Join框架與傳統執行緒池的區別:
        採用“工作竊取”模式(work-stealing):當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到執行緒佇列中,然後再從一個隨機執行緒的佇列中偷一個並把它放在自己的佇列中。相對於一般的執行緒池實現,fork/join框架的優勢體現在對其中包含的任務的處理方式上.在一般的執行緒池中,如果一個執行緒正在執行的任務由於某些原因無法繼續執行,那麼該執行緒會處於等待狀態.而在fork/join框架實現中,如果某個子問題由於等待另外一個子問題的完成而無法繼續執行.那麼處理該子問題的執行緒會主動尋找其他尚未執行的子問題來執行.這種方式減少了執行緒的等待時間,提高了效能.

三 並行流與其他方式對比

下面就以一個求 0到一億、十億、五百億 的和來比較執行的效率:

1) 使用For迴圈求和

	@Test
	public void testFor() {
		Instant start = Instant.now();
		long sum = 0;
		for (long i = 0; i <= 50000000000L; i++) {
			 sum += i;
		} 
		System.out.println(sum);
		Instant end = Instant.now();
		System.out.println("五百億求和花費的時間為: " + Duration.between(start, end).toMillis());
	}
執行結果:
5000000050000000
一億求和花費的時間為: 91

500000000500000000
十億求和花費的時間為: 868

-4378596987249509888
五百億求和花費的時間為: 33910

2) 使用Fork/Join框架求和

package com.lxj.java8;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ForkJoinSum extends  RecursiveTask<Long>{

	private static final long serialVersionUID = 6011408981548802596L;
	
	private long start;
	private long end;
	//臨界值
	private final long THRESHHOLD = 10000L; 

	public ForkJoinSum() {
		
	}
	public ForkJoinSum(long start, long end) {
		super();
		this.start = start;
		this.end = end;
	}
	
	@Override
	protected Long compute() {
		if(end - start <= THRESHHOLD) {
			long sum = 0L;
			for (long i = start; i <= end; i++) {
				sum += i;
			}
			return sum;
		}else {
			long mid = (start + end)/2;
			ForkJoinSum left = new ForkJoinSum(start,mid);
			left.fork(); //分支
			
			ForkJoinSum right = new ForkJoinSum(mid+1,end);
			right.fork(); //分支
			
			return left.join() + right.join(); //合併
		}
	}
	
	public static void main(String[] args) {
		Instant start = Instant.now();  //100000000L   1000000000L  50000000000L
		ForkJoinTask<Long> forkJoinTask = new ForkJoinSum(0L,50000000000L);
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		Long t = forkJoinPool.invoke(forkJoinTask);
		System.out.println(t);
		Instant end = Instant.now();
		System.out.println("十億求和耗費的時間為: " + Duration.between(start, end).toMillis());
	}
	
}
輸出結果:

5000000050000000
一億求和耗費的時間為: 106

500000000500000000
十億求和耗費的時間為: 508

-4378596987249509888
五百億求和耗費的時間為: 20256

 3)   使用自己寫的遞迴求和(絕對是個人為了好玩才測的,就怕棧被資料撐爆了,要是C語言的話估計早遞迴爆記憶體了)

package com.lxj.java8;

import java.time.Duration;
import java.time.Instant;

public class Recursion {

	private long start;
	private long end;
	private final long THRESHHOLD = 10000L;

	public Recursion(long start, long end) {
		super();
		this.start = start;
		this.end = end;
	}
	
	public Long getValue() {
		long t = (start + end)/2;
		if(end - start <= THRESHHOLD) {
			long sum = 0L;
			for(long i = start ; i <= end ; i++) {
				sum += i;
			}
			return sum;
		}else {
			Recursion left = new Recursion(start, t); 
			Recursion right = new Recursion(t+1, end); 
			return left.getValue() + right.getValue();
		}
	}
	
	//100000000L   1000000000L  50000000000L
	/*
		5000000050000000
		一億求和耗費的時間為: 182
		
		500000000500000000
		十億求和耗費的時間為: 979
		
		-4378596987249509888
		五百億求和耗費的時間為: 114102

	 */
	public static void main(String[] args) {
		Instant start = Instant.now();
		Recursion binaryValue = new Recursion(0L, 50000000000L);
		Long value = binaryValue.getValue();
		System.out.println(value);
		Instant end = Instant.now();
		System.out.println("五百億求和耗費的時間為: "+Duration.between(start, end).toMillis());
	}
	
	
}
執行結果:

5000000050000000
一億求和耗費的時間為: 182
		
500000000500000000
十億求和耗費的時間為: 979
		
-4378596987249509888
五百億求和耗費的時間為: 114102

 4) 使用StreamAPI

	@Test
	public void testStream() {
		Instant start = Instant.now();
		//使用StreamAPI
		OptionalLong result = LongStream.rangeClosed(0L, 50000000000L)
		          .parallel()          
		          .reduce(Long::sum);
		System.out.println(result.getAsLong());
		Instant end = Instant.now();
		System.out.println("五百億求和耗費的時間為: " + Duration.between(start, end).toMillis());
	}
執行結果:
		5000000050000000
		一億求和耗費的時間為: 112
				
		500000000500000000
		十億求和耗費的時間為: 854
				
		-4378596987249509888
		五百億求和耗費的時間為: 20250

通過對比,Stream和Fork/Join框架在大資料的時候速度還是挺快的,For迴圈在資料小的時候是最快的。

 

 

 

 

 

相關文章