Следует отметить, что использование lamda с Stream
заманчиво, а когда мы работаем с Stream
, легко добавить вызов .parallel
для использования многопоточности. Это добавляет проблему с параллелизмом.
Одна хорошая вещь - это то, что можно решить с помощью класса, который также может помочь вам с вашей задачей: AtomicInteger
предоставляет метод для увеличения AtomicInteger.incAndGet
или для добавления AtomicInteger.addAndGet
Это можно использовать как ссылку на метод, например:
final int SIZE = 10_000;
AtomicInteger cnt = new AtomicInteger();
IntStream.range(0, SIZE)
.parallel().map(i -> 1)
.forEach(cnt::addAndGet);
Это не самый интересный код, но он будет увеличиваться с использованием синхронизированного счетчика, предотвращая проблемы. Я использовал эквивалентный код, используя простой счетчик с int
для сравнения, и почти при каждой попытке он не работает.
Вот быстрый код для подтверждения концепции. Это простой цикл, который будет пытаться 100 раз каждый тест и выводить в консоли, если есть проблема.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Main {
int cntInt;
AtomicInteger cntAtom;
final int SIZE = 10_000;
final int TRIES = 1_000;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Main().testInt();
}
for (int i = 0; i < 100; i++) {
new Main().testAtomic();
}
}
public void testInt(){
int testCount = 0;
do{
cntInt = 0;
testCount++;
IntStream.range(0, SIZE)
.parallel().map(i -> 1)
.forEach(i -> cntInt += i);
}while(cntInt == SIZE && testCount < TRIES);
if(cntInt != SIZE ){
System.out.format("INTEGER Run: %d, Value: %d, Expected: %d%n", testCount, cntInt, SIZE);
}
}
public void testAtomic(){
int testCount = 0;
do{
cntAtom = new AtomicInteger();
testCount++;
IntStream.range(0, SIZE)
.parallel().map(i -> 1)
.forEach(cntAtom::addAndGet);
}while(cntAtom.get() == SIZE&& testCount < TRIES);
if(cntAtom.get() != SIZE ){
System.out.format("ATOMIC Run: %d. Value: %dm Expected: %d%n", testCount, cntAtom.get(), SIZE);
}
}
}
Иногда INTEGER может работать довольно долго без каких-либо проблем, но вы заметите, что использование AtomicInteger
и синхронизация более безопасны.