I have read that DecimalFormat is not thread safe. I wanted to check the correct way of using it inside parallelStream. See my sample code:
import org.junit.jupiter.api.Test;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
public class ThreadTest {
private final static DecimalFormat df = new DecimalFormat("#.##");
private static final ThreadLocal<DecimalFormat> FORMATTER =
new ThreadLocal<DecimalFormat>() {
@Override
protected DecimalFormat initialValue() {
return new DecimalFormat("#.##");
}
};
@Test
public void test() {
List<Double> numberList = new ArrayList<>();
Random r = new Random();
for(int i=0;i<1000;++i) {
numberList.add(r.nextDouble() * 100);
}
double sum = 0.0;
for(int i=0;i<1000;++i) {
sum = sum + Double.parseDouble(df.format(numberList.get(i)));
}
System.out.println(sum);
AtomicReference<Double> sum6 = new AtomicReference<>(0.0);
numberList.stream().parallel().forEach(aDouble -> {
double val = Double.parseDouble(FORMATTER.get().format(aDouble));
sum6.set(sum6.get() + val);
});
System.out.println(sum6);
}
}
In the above test case, output of sum
and sum6
is coming out to be different even though the sum6
is using ThreadLocal
instance.
Can anyone help me what I am doing wrong?
If you want to use AtomicReference
, don't use its set
method like DuncG already said. Instead use its accumulateAndGet
or getAndAccumulate
method (since you don't care about the result it doesn't matter which one):
sum6.accumulateAndGet(val, (x, y) -> x + y);
Slightly better is to use DoubleAdder
instead, as it has a simple add
method.
The best solution however is to not update any intermediate object yourself, and let the stream collect / reduce for you:
double sum6 = numberList.stream()
.parallel()
.mapToDouble(aDouble -> Double.parseDouble(FORMATTER.get().format(aDouble)))
.sum();