从Java parallelstream产生其他parallelStream并且很少失败

时间:2015-05-08 11:25:44

标签: java lambda parallel-processing java-8 java-stream

考虑以下功能:

public void execute4() {
        File filePath = new File(filePathData);
        File[] files = filePath.listFiles((File filePathData) -> filePathData.getName().endsWith("CDR"));
        List<CDR> cdrs = new ArrayList<CDR>();
        Arrays.asList(files).parallelStream().forEach(file -> readCDRP(cdrs, file));
        cdrs.sort(cdrsorter);
    }

读取包含CDR的文件列表并执行readCDRP(),即:

private void readCDRP(List<CDR> cdrs, File file) {
    final CDR cdr = new CDR(file.getName());
    try (BufferedReader bfr = new BufferedReader(new FileReader(file))) {
        List<String> lines = bfr.lines().collect(Collectors.toList());
        lines.parallelStream().forEach(e -> {
            String[] data = e.split(",", -1);
            CDREntry entry = new CDREntry(file.getName());
            for (int i = 0; i < data.length; i++) {
                entry.setField(i, data[i]);
            }
            cdr.addEntry(entry);
        });

        if (cdr != null) {
            cdrs.add(cdr);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

我观察到的是偶尔而不是所有的时间,我在readCDRP函数上得到一个ArrayIndexNotBound Exception(这很尴尬,因为cdr的列表是一个ArrayList() ):

cdr.addEntry(entry);

或 在execute4()的最后一行,我在那里应用排序。

我认为问题是来自execute4的第一个parallelStream不在readCDRP()内的第二个parallelStream执行的内存中的单独空间中,并且似乎也错误地共享数据。使用&#34;似乎&#34;我无法确认,只是一个厨房。

问题是: 从JDK8的角度来看,我的代码是错误的吗? 是否有使用相同流程的解决方法,例如使用CountDownLatch? 是对ForkJoinPool的限制吗?

感谢您的回应......

修改(1): addEntry是类本身的一部分:

class CDR {
        public final String fileName;
        private final List<CDREntry> entries = new ArrayList<CDREntry>();

        public CDR(String fileName) {
            super();
            this.fileName = fileName;
        }

        public List<CDREntry> getEntries() {
            return entries;
        }

        public List<CDREntry> addEntry(CDREntry e) {
            entries.add(e);
            return entries;
        }

        public String getFileName() {
            return this.fileName;
        }
    }

3 个答案:

答案 0 :(得分:4)

从线程安全的角度来看,您的代码已被破坏。在<a target="_blank" href="http://gerster.com/docs/posamenten_neuheiten_2014_2.pdf">Jetzt PDF-Katalog herunterladen</a> 中,您向readCDR列表添加元素,该列表是不支持并发写入的cdrs。这就是它破裂的原因。

更好的方法是让ArrayList返回readCDR个对象并执行以下操作:

cdr

此外,使用并行流进行IO相关操作通常是一个坏主意,但这是另一个讨论。

答案 1 :(得分:3)

当您以函数样式开始编程时,您应该首选可以通过构造完全创建的不可变对象(或者可能使用构建器模式或某些工厂方法)。所以你的CDREntry课程可能如下所示:

class CDREntry {
    private final String[] fields;
    private final String name;

    public CDREntry(String name, String[] fields) {
        this.name = name;
        this.fields = fields;
    }
    // Add getters and whatever
}

您的CDR课程可能如下所示:

class CDR {
    private final String fileName;
    private final List<CDREntry> entries;

    public CDR(String fileName, List<CDREntry> entries) {
        this.fileName = fileName;
        this.entries = entries;
    }

    public List<CDREntry> getEntries() {
        return entries;
    }

    public String getFileName() {
        return this.fileName;
    }
}

拥有这些课程变得更容易。其余的代码可以像这样重写:

public void execute4() {
    File filePath = new File(filePathData);
    File[] files = filePath.listFiles((File data, String name) -> 
             data.getName().endsWith("CDR")); // fixed this line: it had compilation error
    List<CDR> cdrs = Arrays.stream(files).parallel()
            .map(this::readCDRP).sorted(cdrsorter)
            .collect(Collectors.toList());
}

private CDR readCDRP(File file) {
    try (BufferedReader bfr = new BufferedReader(new FileReader(file))) {
        // I'm not sure that collecting lines into list 
        // before main processing was actually necessary
        return bfr.lines().parallelStream()
                .map(e -> new CDREntry(file.getName(), e.split(",", -1)))
                .collect(Collectors.collectingAndThen(
                        Collectors.toList(), list -> new CDR(file.getName(), list)));
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}

一般情况下,forEach通常不是解决任务的最简洁方法。将流集成到遗留代码中可能会有所帮助,但通常应该避免使用。

答案 2 :(得分:2)

您正在使用并行流和具有副作用的lambda (lambda更新了ArrayList'cdrs') 尝试使用收集器或还原操作。