What are the performance impacts of using DecimalFormat with ThreadLocal?

I have an implementation which uses DecimalFormat(https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html) API.

Solution1: The argument is because DecimalFormat is NOT thread-safe, I am inclined to use ThreadLocal for DecimalFormat creation to make it thread safe. Also, it will save creation of DecimalFormat object for each invocation

private static final ThreadLocal<DecimalFormat> restrictTo1DecimalPlace =
            ThreadLocal.withInitial
                    (() -> new DecimalFormat("0.0%", DecimalFormatSymbols.getInstance(Locale.ENGLISH)));

Solution 2: Another simple solution is to give away object reusability and create object of DecimalFormat everytime.

new DecimalFormat("0.0%", DecimalFormatSymbols.getInstance(Locale.ENGLISH)).format(decimalValueToFormat)

What is better?

In most applications, the difference will not matter, so you’ll want the simpler option.

You can verify this by benchmarking both alternatives:

public abstract class Benchmark {

    public static void main(String[] args) throws Exception {
        final ThreadLocal<DecimalFormat> restrictTo1DecimalPlace =
                ThreadLocal.withInitial
                        (() -> new DecimalFormat("0.0%", DecimalFormatSymbols.getInstance(Locale.ENGLISH)));            
        Benchmark[] marks = {
            new Benchmark("ThreadLocal") {
                @Override
                protected Object run(int iterations) throws Throwable {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < iterations; i++) {
                        sb.append(restrictTo1DecimalPlace.get().format(i * 0.01));
                    }
                    return sb;
                };
            },
            new Benchmark("new Format") {
                @Override
                protected Object run(int iterations) throws Throwable {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < iterations; i++) {
                        sb.append(new DecimalFormat("0.0%", DecimalFormatSymbols.getInstance(Locale.ENGLISH)).format(i * 0.01));
                    }
                    return sb;
                };
            },
        };
        for (Benchmark mark : marks) {
            System.out.println(mark);
        }
    }

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "/t" + time() + " ns / iteration";
    }

    private BigDecimal time() {
        try {
            // automatically detect a reasonable iteration count (and trigger just in time compilation of the code under test)
            int iterations;
            long duration = 0;
            for (iterations = 1; iterations < 1_000_000_000 && duration < 1_000_000_000; iterations *= 2) {
                long start = System.nanoTime();
                run(iterations);
                duration = System.nanoTime() - start;
            }
            return new BigDecimal((duration) * 1000 / iterations).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Executes the code under test.
     * @param iterations
     *            number of iterations to perform
     * @return any value that requires the entire code to be executed (to
     *         prevent dead code elimination by the just in time compiler)
     * @throws Throwable
     *             if the test could not complete successfully
     */
    protected abstract Object run(int iterations) throws Throwable;
}

On my machine, this prints:

ThreadLocal 260.132 ns / iteration
new Format  363.199 ns / iteration

So the difference between getting the format from a ThreadLocal or creating a new one was about 0.0000001 seconds. Unless your application formats millions of strings every second, this is not worth thinking about 🙂

I wonder if there is a significant difference

In recent times, JVM performance has been multiplied manifold and so object creation is no longer considered as expensive as it was done earlier.

But it’s true that the Allocation of new DecimalFormat has memory impact that may also consume time.
It’s very reasonable to create new instances, and that’s how java code is supposed to work.

One thing you can try is to pre-allocate DecimalFormat instances and keep them in a simple object pool.