Java 8并行流混乱/问题

时间:2018-10-16 06:01:48

标签: java java-8 java-stream

我是并行流的新手,并尝试制作1个示例程序,该程序将计算* 100(1到100)的值并将其存储在map中。 在执行代码时,每次迭代的计数都不同。 我在某处可能是错的,所以请任何知道的正确方法指导我。

代码

import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.stream.Collectors;

public class Main{    
    static int l = 0;       
    public static void main (String[] args) throws java.lang.Exception {
        letsGoParallel();
    }       
    public static int makeSomeMagic(int data) {
        l++;
        return data * 100;
    }        
    public static void letsGoParallel() {
        List<Integer> dataList = new ArrayList<>();
        for(int i = 1; i <= 100 ; i++) {
            dataList.add(i);
        }
        Map<Integer, Integer> resultMap = new HashMap<>();
        dataList.parallelStream().map(f -> {
            Integer xx = 0;
            {
                xx = makeSomeMagic(f);
            }
            resultMap.put(f, xx);
            return 0;
        }).collect(Collectors.toList());
        System.out.println("Input Size: " + dataList.size());
        System.out.println("Size: " + resultMap.size());
        System.out.println("Function Called: " + l);
    }
}

Runnable Code

最后输出

  

输入大小:100

     

大小:100

     

调用的函数:98

每次运行输出都不同。 我想在自己的应用程序中使用并行流,但是由于这种混乱/问题,我不能这样做。 在我的应用程序中,我有100-200个唯一编号,需要对它们执行一些相同的操作。简而言之,有处理某些内容的功能。

4 个答案:

答案 0 :(得分:5)

您对HashMapl变量的访问都是不是线程安全的,这就是为什么每次运行的输出都不同的原因。

要做的正确方法是将Stream元素收集到Map中:

Map<Integer, Integer> resultMap =
    dataList.parallelStream()
            .collect(Collectors.toMap (Function.identity (), Main::makeSomeMagic));

编辑:l变量仍使用此代码以线程安全的方式进行更新,因此,如果变量的最终值是必需的,则必须添加自己的线程安全性对你很重要。

答案 1 :(得分:2)

通过在resultMap中输入一些值,您使用的是side-effect

 dataList.parallelStream().map(f -> {
            Integer xx = 0;
            {
                xx = makeSomeMagic(f);
            }
            resultMap.put(f, xx);
            return 0;
        })

API指出:

  

无状态操作(例如过滤器和映射)不会保留以下状态:   处理新元素时先前看到的元素-每个元素   可以独立于其他元素上的操作进行处理。

继续进行with

  

如果以下原因导致流管道结果可能不确定或不正确   流操作的行为参数是有状态的。一种   有状态的lambda(或其他实现了适当的   功能接口)是一个其结果取决于任何状态的接口   在执行流水线期间可能会发生变化。

它遵循与您的示例相似的示例:

  

...如果并行执行映射操作,则结果为   由于线程调度,相同的输入可能因运行而异   差异,而使用无状态lambda表达式时,结果   永远都是一样的。

这解释了您的观察:每次运行输出都不同。

正确的方法是@Eran的shown

答案 2 :(得分:0)

希望它能正常工作。通过使Synchronied函数makeSomeMagic并使用Threadsafe数据结构ConcurrentHashMap 并编写简单的语句

dataList.parallelStream().forEach(f -> resultMap.put(f, makeSomeMagic(f)));

整个代码在这里:

import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.stream.Collectors;

public class Main{  
static int l = 0;
  public static void main (String[] args) throws java.lang.Exception {
    letsGoParallel();
  }
  public synchronized static int makeSomeMagic( int data) { // make it synchonized
    l++;
    return data * 100;
  }
  public static void letsGoParallel() {
    List<Integer> dataList = new ArrayList<>();
    for(int i = 1; i <= 100 ; i++) {
      dataList.add(i);
    }
    Map<Integer, Integer> resultMap = new ConcurrentHashMap<>();// use ConcurrentHashMap
    dataList.parallelStream().forEach(f -> resultMap.put(f, makeSomeMagic(f)));
    System.out.println("Input Size: " + dataList.size());
    System.out.println("Size: " + resultMap.size());
    System.out.println("Function Called: " + l);
  }
}

答案 3 :(得分:0)

  • 无需计算该方法被调用的次数。
  • Stream将帮助您循环执行字节码。
  • 将您的逻辑(函数)传递给Stream,不要在多线程(包括parallelStream)中不使用线程安全变量

像这样

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ParallelStreamClient {
//  static int l = 0;---> no need to count times.

    public static void main(String[] args) throws java.lang.Exception {
        letsGoParallel();
    }

    public static int makeSomeMagic(int data) {
//  l++;-----> this is no thread-safe way
    return data * 100;
}

public static void letsGoParallel() {
    List<Integer> dataList = new ArrayList<>();
    for (int i = 1; i <= 100; i++) {
        dataList.add(i);
    }
    Map<Integer, Integer> resultMap =         
    dataList.parallelStream().collect(Collectors.toMap(i -> i,ParallelStreamClient::makeSomeMagic));
    System.out.println("Input Size: " + dataList.size());
    System.out.println("Size: " + resultMap.size());
    //System.out.println("Function Called: " + l);       
}