Search code examples
javamultithreadingjava-streamthread-safety

Using DecimalFormat inside parallel stream


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?


Solution

  • 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();