最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于Java8 parallelStream并發(fā)安全的深入講解

 更新時(shí)間:2018年10月31日 15:36:17   作者:puyangsky  
這篇文章主要給大家介紹了關(guān)于Java8 parallelStream并發(fā)安全的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

背景

Java8的stream接口極大地減少了for循環(huán)寫法的復(fù)雜性,stream提供了map/reduce/collect等一系列聚合接口,還支持并發(fā)操作:parallelStream。

在爬蟲開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)遇到遍歷一個(gè)很大的集合做重復(fù)的操作,這時(shí)候如果使用串行執(zhí)行會(huì)相當(dāng)耗時(shí),因此一般會(huì)采用多線程來(lái)提速。Java8的paralleStream用fork/join框架提供了并發(fā)執(zhí)行能力。但是如果使用不當(dāng),很容易陷入誤區(qū)。

Java8的paralleStream是線程安全的嗎

一個(gè)簡(jiǎn)單的例子,在下面的代碼中采用stream的forEach接口對(duì)1-10000進(jìn)行遍歷,分別插入到3個(gè)ArrayList中。其中對(duì)第一個(gè)list的插入采用串行遍歷,第二個(gè)使用paralleStream,第三個(gè)使用paralleStream的同時(shí)用ReentryLock對(duì)插入列表操作進(jìn)行同步:

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("串行執(zhí)行的大?。? + list1.size());
 System.out.println("并行執(zhí)行的大?。? + list2.size());
 System.out.println("加鎖并行執(zhí)行的大?。? + list3.size());
}

執(zhí)行結(jié)果:

串行執(zhí)行的大?。?0000
并行執(zhí)行的大?。?595
加鎖并行執(zhí)行的大小:10000

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

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

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

在上面兩個(gè)問(wèn)題的解答中,證實(shí)paralleStream的forEach接口確實(shí)不能保證同步,同時(shí)也提出了解決方案:使用collect和reduce接口。

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

在Javadoc中也對(duì)stream的并發(fā)操作進(jìn)行了相關(guān)介紹:

The Collections Framework provides synchronization wrappers, which add automatic synchronization to an arbitrary collection, making it thread-safe.

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

所以下一步,來(lái)看看collect接口如何使用。

stream的collect接口

閑話不多說(shuō)直接上源碼吧,Stream.java中的collect方法句柄:

<R, A> R collect(Collector<? super T, A, R> collector);

在該實(shí)現(xiàn)方法中,參數(shù)是一個(gè)Collector對(duì)象,可以使用Collectors類的靜態(tài)方法構(gòu)造Collector對(duì)象,比如Collectors.toList(),toSet(),toMap(),etc,這塊很容易查到API故不細(xì)說(shuō)了。

除此之外,我們?nèi)绻赾ollect接口中做更多的事,就需要自定義實(shí)現(xiàn)Collector接口,需要實(shí)現(xiàn)以下方法:

Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();

要輕松理解這三個(gè)參數(shù),要先知道fork/join是怎么運(yùn)轉(zhuǎn)的,一圖以蔽之:

上圖來(lái)自:http://www.infoq.com/cn/articles/fork-join-introduction

簡(jiǎn)單地說(shuō)就是大任務(wù)拆分成小任務(wù),分別用不同線程去完成,然后把結(jié)果合并后返回。所以第一步是拆分,第二步是分開(kāi)運(yùn)算,第三步是合并。這三個(gè)步驟分別對(duì)應(yīng)的就是Collector的supplier,accumulator和combiner。talk is cheap show me the code,下面用一個(gè)例子來(lái)說(shuō)明:

輸入是一個(gè)10個(gè)整型數(shù)字的ArrayList,通過(guò)計(jì)算轉(zhuǎn)換成double類型的Set,首先定義一個(gè)計(jì)算組件:

Compute.java:

public class Compute {
public Double compute(int num) {
 return (double) (2 * num);
}
}

接下來(lái)在Main.java中定義輸入的類型為ArrayList的nums和類型為Set的輸出結(jié)果result:

private List<Integer> nums = new ArrayList<>();
private Set<Double> result = new HashSet<>();

定義轉(zhuǎn)換list的run方法,實(shí)現(xiàn)Collector接口,調(diào)用內(nèi)部類Container中的方法,其中characteristics()方法返回空set即可:

public void run() {
 // 填充原始數(shù)據(jù),nums中填充0-9 10個(gè)數(shù)
 IntStream.range(0, 10).forEach(nums::add);
 //實(shí)現(xiàn)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();
 }
 });
}

構(gòu)造內(nèi)部類Container,該類的作用是一個(gè)存放輸入的容器,定義了三個(gè)方法:

  • accumulate方法對(duì)輸入數(shù)據(jù)進(jìn)行處理并存入本地的結(jié)果
  • combine方法將其他容器的結(jié)果合并到本地的結(jié)果中
  • getResult方法返回本地的結(jié)果

Container.java:

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中編寫測(cè)試方法:

public static void main(String[] args) {
 Main main = new Main();
 main.run();
 System.out.println("原始數(shù)據(jù):");
 main.nums.forEach(i -> System.out.print(i + " "));
 System.out.println("\n\ncollect方法加工后的數(shù)據(jù):");
 main.result.forEach(i -> System.out.print(i + " "));
}

輸出:

原始數(shù)據(jù):
0 1 2 3 4 5 6 7 8 9

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

我們將10個(gè)整型數(shù)值的list轉(zhuǎn)成了10個(gè)double類型的set,至此驗(yàn)證成功~

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

一言蔽之

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

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • IDEA插件推薦之Maven-Helper的教程圖解

    IDEA插件推薦之Maven-Helper的教程圖解

    這篇文章主要介紹了IDEA插件推薦之Maven-Helper的相關(guān)知識(shí),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考
    2020-07-07
  • Java 如何快速實(shí)現(xiàn)一個(gè)連接池

    Java 如何快速實(shí)現(xiàn)一個(gè)連接池

    有沒(méi)有一個(gè)通用的庫(kù)可以快速實(shí)現(xiàn)一個(gè)線程池呢?得益于 Java 完善的生態(tài),前人們針對(duì)這種需要開(kāi)發(fā)了一個(gè)通用庫(kù):Apache Commons Pool(下文簡(jiǎn)稱 ACP)。本質(zhì)上來(lái)說(shuō),ACP 庫(kù)提供的是管理對(duì)象池的通用能力,當(dāng)然也可以用來(lái)管理連接池了!
    2021-05-05
  • Java?流處理之收集器詳解

    Java?流處理之收集器詳解

    這篇文章主要介紹了Java?流處理之收集器,本文以記錄?Record?為例,結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • Java的引用類型常用的四種方法

    Java的引用類型常用的四種方法

    這篇文章主要介紹了Java的引用類型常用的幾種方法,Java為引用類型專門定義了一個(gè)類Reference,它是引用對(duì)象的抽象基類,相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-06-06
  • SpringBoot配置攔截器的示例

    SpringBoot配置攔截器的示例

    這篇文章主要介紹了SpringBoot配置攔截器的示例,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-11-11
  • java讀取excel表格的方法

    java讀取excel表格的方法

    這篇文章主要為大家詳細(xì)介紹了java讀取excel表格的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • Failed to execute goal org...的解決辦法

    Failed to execute goal org...的解決辦法

    這篇文章主要介紹了Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1的解決辦法的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Java Long類型對(duì)比分析

    Java Long類型對(duì)比分析

    這篇文章主要介紹了Java Long類型對(duì)比分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java解析方法的調(diào)用關(guān)系示例詳解

    Java解析方法的調(diào)用關(guān)系示例詳解

    這篇文章主要介紹了Java解析方法的調(diào)用關(guān)系,方法在Java編程中用于封裝重復(fù)的代碼,提高代碼復(fù)用性,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-10-10
  • Java使用HashMap實(shí)現(xiàn)并查集

    Java使用HashMap實(shí)現(xiàn)并查集

    這篇文章主要為大家詳細(xì)介紹了Java使用HashMap實(shí)現(xiàn)并查集,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11

最新評(píng)論

潮安县| 荣昌县| 宣化县| 云龙县| 新巴尔虎右旗| 和硕县| 博罗县| 卫辉市| 德钦县| 南开区| 宜良县| 永德县| 昌图县| 汤原县| 东光县| 灵丘县| 女性| 卓尼县| 瓦房店市| 崇仁县| 临猗县| 普兰县| 合江县| 祁门县| 鄂尔多斯市| 宁德市| 太保市| 涿鹿县| 孝感市| 邢台市| 田林县| 高安市| 庆城县| 南投市| 清丰县| 维西| 新营市| 祁阳县| 木兰县| 峨眉山市| 得荣县|