使用Stream / parallelStream将String插入StringBuffer / StringBuilder时出现问题

时间:2019-05-30 21:32:42

标签: java java-stream stringbuilder stringbuffer

我试图使用从Set集合创建的parallelStream()的foreach方法将字符串插入StringBuffer。 问题是每次执行代码时,最终的String(StringBuffer.toString())的总数要少1个元素(每次尝试都为随机元素)。

我也将StringBuffer更改为StringBuilder,将parallelStream()更改为stream(),但总是少了1个元素。

我正在使用: -Java版本:java 1.8_121 -服务器:Weblogic 12.2.1.2(我认为这与问题无关) -Spring boot 2.0.2.RELEASE(我认为这与问题无关)

注意:我使用了Map来保存稍后应在进程中签名的pdf(在另一个HTTP请求中)。

Map<String, ClientPdf> dataToEncript = new HashMap<>(); // pdf name it will be the key for this map (it is unique in the sql query)

List<Client> listClients = // list of clients from database
Set<ClientPdf> clientsPdf = new HashSet<>();
for (Client client : listClients) { 
    clientsPdf.add(client.clientPdf()); // clientPdf() generate a new object ClientPdf, which is similar to Client class, but with less fields (essential for the Set)
}

log.debug("Generating documents");
clientsPdf.parallelStream().forEach(pdf -> {
    // some code to generate pdf

    log.debug("Inserting pdf: {}", pdf); // this log print, for example, 27.000 lines
    dataToEncript.put(pdf.getPdfName(), pdf);
});


StringBuffer sb = new StringBuffer(); // StringBuffer or StringBuilder, the same problem
for (ClientPdf clientPdf : dataToEncript.values()) {
    sb.append(clientPdf.getPdfName() + ";" + clientPdf.getRut() + "\n"); // appending all values of de map dataToEncript, it will append 26.669 (1 less)
}

3 个答案:

答案 0 :(得分:4)

clientsPdf.parallelStream().forEach(pdf -> {
    // ... 
    dataToEncript.put(pdf.getPdfName(), pdf);
});

dataToEncript并非线程安全的数据结构,因此这可能会导致荒谬和奇怪的错误,如您正在观察的错误

通常,使用forEach通常是一个不好的信号,并且您应该几乎总是使用Collector或其他方法。例如,在这里您应该使用

clientsPdf.parallelStream()
   .collect(Collectors.toConcurrentMap(Pdf::getPdfName, pdf -> pdf));

获取正确的地图。

更好的是,你可以写

clientsPdf.parallelStream()
    .map(clientPdf -> clientPdf.getPdfName() + ";" + clientPdf.getRut() + "\n")
    .collect(Collectors.joining())

获得最终的String,而无需手动管理StringBuffer等。

答案 1 :(得分:0)

因为HashMap如上Wasserman所述不是线程安全的
如果多个线程正在访问同一对象并尝试修改其结构,则可能导致HashMap状态的不一致。

因此,引入了HashTableSynchronizedMapConcurrentHashMap以在多线程环境(例如HashMap)中使用parallelStream()

您可以按如下所示简单地重写代码的第一行:

Map<String, ClientPdf> dataToEncript = Collections.synchronizedMap(new HashMap<>());

现在,应该重新运行程序后才能获得正确的结果。

顺便说一句,HashTableSynchronizedMap的性能都不好,您可以使用ConcurrentHashMap来解决此问题。

祝你好运!

答案 2 :(得分:-1)

尝试使用数组而不是hashMap。我在与地图不同的上下文中遇到了类似的问题。因此,您应该使用stringToEncrypt和clientPdfToEncrypt或类似的东西来替换dataToEncrypt。 无论如何,您还可以对dataToEncrypt,listClients和clientsPdf进行调试,以准确确定错误发生的位置。