Ошибка вставки String в StringBuffer / StringBuilder с помощью stream / parallelStream - PullRequest
0 голосов
/ 31 мая 2019

Я пытался вставить Strings в StringBuffer, используя метод foreach функции parallelStream (), созданный из коллекции Set.Проблема в том, что каждый раз, когда я выполняю код, конечная строка (StringBuffer.toString ()) имеет на 1 элемент меньше (случайный элемент каждый раз, когда я пытаюсь).

Я также изменяю StringBuffer на StringBuilder,the ParallelsStream () to stream (), но всегда содержит на 1 элемент меньше.

Я использую: - Java-версию: java 1.8_121 - Сервер: Weblogic 12.2.1.2 (я не думаю, что это имеет отношение кпроблема) - Spring boot 2.0.2.RELEASE (я не думаю, что это имеет отношение к проблеме)

ПРИМЕЧАНИЕ: я использовал карту, чтобы сохранить 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)
}

Ответы [ 2 ]

4 голосов
/ 31 мая 2019
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 или подобным.

0 голосов
/ 04 июня 2019

Поскольку HashMap является не поточно-ориентированным , как упоминалось выше Вассерманом.
Это может привести к несогласованности в состоянии HashMap, если несколько потоков обращаются к одному и тому же объекту и пытаются изменить его структуру.

Следовательно, HashTable, SynchronizedMap или ConcurrentHashMap введены для использования HashMap в многопоточной среде (такой как parallelStream()).

Вы можете просто переписать первую строку своего кода следующим образом:

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

Теперь вы должны получить правильный результат после перезапуска вашей программы.

Кстати, и HashTable, и SynchronizedMap не очень хороши по производительности, вместо этого вы можете использовать ConcurrentHashMap.

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...