上期介紹了Java8中
Stream
的新特性翅雏,本期我們將測(cè)試下stream
與parallelStream
的性能以及應(yīng)用的場(chǎng)景。
先上代碼
public class StreamTest {
private static final int MAX_INT = 1_000_000;
public static void stream() {
List<String> list = new ArrayList<>();
IntStream.range(0, MAX_INT).forEach(value -> {
UUID uuid = UUID.randomUUID();
list.add(uuid.toString());
});
long startTime = System.nanoTime();
list.stream().sorted().collect(Collectors.toList());
long endTime = System.nanoTime();
long durationTime = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("stream execute time : " + durationTime);
}
public static void parallelStream() {
List<String> list = new ArrayList<>();
IntStream.range(0, MAX_INT).forEach(value -> {
UUID uuid = UUID.randomUUID();
list.add(uuid.toString());
});
long startTime = System.nanoTime();
list.parallelStream().sorted().collect(Collectors.toList());
long endTime = System.nanoTime();
long durationTime = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("parallelStream execute time : " + durationTime);
}
public static void main(String[] args) {
stream();
parallelStream();
}
}
MAX_INT = 1_000_000; //Jav8中數(shù)字可以用_間隔人芽,類似1,000,000
Max_INT為1時(shí)望几,結(jié)果為:
stream execute time : 6
parallelStream execute time : 8
Max_INT為100時(shí),結(jié)果為:
stream execute time : 7
parallelStream execute time : 7
Max_INT為1_000時(shí)萤厅,結(jié)果為:
stream execute time : 15
parallelStream execute time : 22
Max_INT為10_000時(shí)橄抹,結(jié)果為:
stream execute time : 28
parallelStream execute time : 21
Max_INT為100_000時(shí)靴迫,結(jié)果為:
stream execute time : 98
parallelStream execute time : 62
Max_INT為1_000_000時(shí),結(jié)果為:
stream execute time : 742
parallelStream execute time : 429
Max_INT為5_000_000時(shí)楼誓,結(jié)果為:
stream execute time : 4299
parallelStream execute time : 2191
Max_INT為10_000_000時(shí)玉锌,結(jié)果為:
stream execute time : 9849
parallelStream execute time : 6923
分析
并行適用的場(chǎng)景?
- 有大量的元素要處理
- 性能問題是首要考慮的
- 沒有在一個(gè)多線程的環(huán)境中
所以如Java Web應(yīng)用疟羹,底層都是Servlet主守,我們知道,Servlet是多線程的榄融,所以在web應(yīng)用中并行流并不適用参淫,而對(duì)于數(shù)據(jù)的處理、算法的驗(yàn)證等單線程環(huán)境是適用的愧杯。
原理
并行流底層其實(shí)是ForkJoinPool
涎才,用的是分治法
,即Fork/Join
方法
當(dāng)執(zhí)行新的任務(wù)時(shí)它可以將其拆分分成更小的任務(wù)執(zhí)行力九,并將小任務(wù)加到線 程隊(duì)列中耍铜,然后再?gòu)囊粋€(gè)隨機(jī)線程的隊(duì)列中偷一個(gè)并把它放在自己的隊(duì)列中。
相對(duì)于一般的線程池實(shí)現(xiàn)跌前,fork/join框架的優(yōu)勢(shì)體現(xiàn)在對(duì)其中包含的任務(wù)的處理方式上业扒,在一般的線程池中,如果一個(gè)線程正在執(zhí)行的任務(wù)由于某些原因無法繼續(xù)運(yùn)行,那么該線程會(huì)處于等待狀態(tài)舒萎。而在fork/join框架實(shí)現(xiàn)中,如果某個(gè)子問題由于等待另外一個(gè)子問題的完成而無法繼續(xù)運(yùn)行.那么處理該子問題的線程會(huì)主動(dòng)尋找其他尚未運(yùn)行的子問題來執(zhí)行蹭沛。這種方式減少了線程的等待時(shí)間臂寝,提高了性能。
public static void main(String[] args) {
ForkJoinPool pool = ForkJoinPool.commonPool();
System.out.println(pool.getParallelism());
}
結(jié)果:
3
我們可以通過參數(shù)來修改:
結(jié)果:
10
總結(jié)
如何高效使用并行流摊灭?
- 如果用循環(huán)還是順序流或者是并行流咆贬,像我們上面那樣測(cè)試一下;
- 注意裝箱帚呼,盡量使用
IntStream
,LongStream
掏缎,和DoubleStream
來避免裝箱拆箱; - 有些操作在并行流上性能很差,比如
limit
煤杀,findFirst
等依賴順序的操作眷蜈。unordered
方法可以把有序流轉(zhuǎn)為無序流,使用findAny
等好很多沈自,在無序流上用limit
也好很多; - 計(jì)算流水線操作總成本酌儒,處理單個(gè)元素用時(shí)越多,并行就越劃算枯途;
- 對(duì)于較小的數(shù)據(jù)量忌怎,用并行不一定是好事兒籍滴;
- 區(qū)分單線程和多線程,多線程下并行不一定是好事兒榴啸;
- 數(shù)據(jù)結(jié)果是否易于分解孽惰,比如
ArrayList
比LinkedList
易于分解,range
創(chuàng)建的原始流也易于分解鸥印; - 終端操作中的合并大家是否很大勋功,大了也不劃算。