一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 關于Java8 parallelStream并發安全的深入講解

關于Java8 parallelStream并發安全的深入講解

2021-06-09 13:56puyangsky Java教程

這篇文章主要給大家介紹了關于Java8 parallelStream并發安全的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

背景

java8的stream接口極大地減少了for循環寫法的復雜性,stream提供了map/reduce/collect等一系列聚合接口,還支持并發操作:parallelstream。

在爬蟲開發過程中,經常會遇到遍歷一個很大的集合做重復的操作,這時候如果使用串行執行會相當耗時,因此一般會采用多線程來提速。java8的parallestream用fork/join框架提供了并發執行能力。但是如果使用不當,很容易陷入誤區。

java8的parallestream是線程安全的嗎

一個簡單的例子,在下面的代碼中采用stream的foreach接口對1-10000進行遍歷,分別插入到3個arraylist中。其中對第一個list的插入采用串行遍歷,第二個使用parallestream,第三個使用parallestream的同時用reentrylock對插入列表操作進行同步:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static list<integer> list1 = new arraylist<>();
private static list<integer> list2 = new arraylist<>();
private static list<integer> list3 = new arraylist<>();
private static lock lock = new reentrantlock();
 
public static void main(string[] args) {
 intstream.range(0, 10000).foreach(list1::add);
 
 intstream.range(0, 10000).parallel().foreach(list2::add);
 
 intstream.range(0, 10000).foreach(i -> {
 lock.lock();
 try {
  list3.add(i);
 }finally {
  lock.unlock();
 }
 });
 
 system.out.println("串行執行的大小:" + list1.size());
 system.out.println("并行執行的大小:" + list2.size());
 system.out.println("加鎖并行執行的大小:" + list3.size());
}

執行結果:

串行執行的大小:10000
并行執行的大小:9595
加鎖并行執行的大小:10000

并且每次的結果中并行執行的大小不一致,而串行和加鎖后的結果一直都是正確結果。顯而易見,stream.parallel.foreach()中執行的操作并非線程安全。

那么既然parallestream不是線程安全的,是不是在其中的進行的非原子操作都要加鎖呢?我在stackoverflow上找到了答案:

  • https://codereview.stackexchange.com/questions/60401/using-java-8-parallel-streams
  • https://stackoverflow.com/questions/22350288/parallel-streams-collectors-and-thread-safety

在上面兩個問題的解答中,證實parallestream的foreach接口確實不能保證同步,同時也提出了解決方案:使用collect和reduce接口。

  • http://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html

在javadoc中也對stream的并發操作進行了相關介紹:

the collections framework provides synchronization wrappers, which add automatic synchronization to an arbitrary collection, making it thread-safe.

collections框架提供了同步的包裝,使得其中的操作線程安全。

所以下一步,來看看collect接口如何使用。

stream的collect接口

閑話不多說直接上源碼吧,stream.java中的collect方法句柄:

?
1
<r, a> r collect(collector<? super t, a, r> collector);

在該實現方法中,參數是一個collector對象,可以使用collectors類的靜態方法構造collector對象,比如collectors.tolist(),toset(),tomap(),etc,這塊很容易查到api故不細說了。

除此之外,我們如果要在collect接口中做更多的事,就需要自定義實現collector接口,需要實現以下方法:

?
1
2
3
4
5
supplier<a> supplier();
biconsumer<a, t> accumulator();
binaryoperator<a> combiner();
function<a, r> finisher();
set<characteristics> characteristics();

要輕松理解這三個參數,要先知道fork/join是怎么運轉的,一圖以蔽之:

關于Java8 parallelStream并發安全的深入講解

上圖來自:http://www.infoq.com/cn/articles/fork-join-introduction

簡單地說就是大任務拆分成小任務,分別用不同線程去完成,然后把結果合并后返回。所以第一步是拆分,第二步是分開運算,第三步是合并。這三個步驟分別對應的就是collector的supplier,accumulator和combiner。talk is cheap show me the code,下面用一個例子來說明:

輸入是一個10個整型數字的arraylist,通過計算轉換成double類型的set,首先定義一個計算組件:

compute.java:

?
1
2
3
4
5
public class compute {
public double compute(int num) {
 return (double) (2 * num);
}
}

接下來在main.java中定義輸入的類型為arraylist的nums和類型為set的輸出結果result:

?
1
2
private list<integer> nums = new arraylist<>();
private set<double> result = new hashset<>();

定義轉換list的run方法,實現collector接口,調用內部類container中的方法,其中characteristics()方法返回空set即可:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void run() {
 // 填充原始數據,nums中填充0-9 10個數
 intstream.range(0, 10).foreach(nums::add);
 //實現collector接口
 result = nums.stream().parallel().collect(new collector<integer, container, set<double>>() {
 
 @override
 public supplier<container> supplier() {
  return container::new;
 }
 
 @override
 public biconsumer<container, integer> accumulator() {
  return container::accumulate;
 }
 
 @override
 public binaryoperator<container> combiner() {
  return container::combine;
 }
 
 @override
 public function<container, set<double>> finisher() {
  return container::getresult;
 }
 
 @override
 public set<characteristics> characteristics() {
  // 固定寫法
  return collections.emptyset();
 }
 });
}

構造內部類container,該類的作用是一個存放輸入的容器,定義了三個方法:

  • accumulate方法對輸入數據進行處理并存入本地的結果
  • combine方法將其他容器的結果合并到本地的結果中
  • getresult方法返回本地的結果

container.java:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class container {
 // 定義本地的result
 public set<double> set;
 
 public container() {
 this.set = new hashset<>();
 }
 
 public container accumulate(int num) {
 this.set.add(compute.compute(num));
 return this;
 }
 
 public container combine(container container) {
 this.set.addall(container.set);
 return this;
 }
 
 public set<double> getresult() {
 return this.set;
 }
}

在main.java中編寫測試方法:

?
1
2
3
4
5
6
7
8
public static void main(string[] args) {
 main main = new main();
 main.run();
 system.out.println("原始數據:");
 main.nums.foreach(i -> system.out.print(i + " "));
 system.out.println("\n\ncollect方法加工后的數據:");
 main.result.foreach(i -> system.out.print(i + " "));
}

輸出:

原始數據:
0 1 2 3 4 5 6 7 8 9

collect方法加工后的數據:
0.0 2.0 4.0 8.0 16.0 18.0 10.0 6.0 12.0 14.0

我們將10個整型數值的list轉成了10個double類型的set,至此驗證成功~

本程序參考 http://blog.csdn.net/io_field/article/details/54971555。

一言蔽之

總結就是parallestream里直接去修改變量是非線程安全的,但是采用collect和reduce操作就是滿足線程安全的了。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://www.cnblogs.com/puyangsky/p/7608741.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产欧美日韩综合二区三区 | 奇米影视4444 | 三星w699 | 国产精品成人一区二区1 | 香蕉在线播放 | 性欧美黑人巨大喷潮xxoo | www.av在线视频 | 99久久香蕉国产线看观香 | 国产精品xxxav免费视频 | 日日夜夜撸影院 | 人妖巨茎video| 性派对videofreeparty| 爱情岛永久成人免费网站 | 男人的天堂欧美 | 操碰人人 | 99影视在线视频免费观看 | 四虎永久在线精品免费影视 | 99福利影院 | 久久99国产综合精品AV蜜桃 | 成人午夜毛片 | 石原莉奈被店长侵犯免费 | 欧美性色黄大片四虎影视 | 东北老女人91p0rny | 欧洲老太玩小伙 | 国产精品片 | 精品福利视频一区二区三区 | 美女脱小内内给男生摸j | 日本免费的一级绿象 | 欧美国产合集在线视频 | 成人欧美视频在线看免费 | 加勒比一本大道香蕉在线视频 | 四虎永久免费地址在线网站 | 欧美交换乱理伦片120秒 | 经典千人斩一区二区视频 | 免费国产福利 | 亚洲欧美视频在线播放 | 日韩国产欧美一区二区三区 | 欧美一级在线 | 精品视频一区二区三区 | 久久99re8热在线播放 | 日本黄色一区 |