Pages

Thursday, January 19, 2012

Synchronized vs ThreadLocal SimpleDateFormat

EDIT: This benchmark is not really reliable, as any other benchmark that is done just for fun - but here is the new one that pretends to have more sense.
It is widely known that SimpleDateFormat on the Java platform is not thread-safe. There are basically three approaches to deal with this unsafety. First - each time create new instance. Should be not very good, since it is not very simple and lightweight object. Synchronizing - I think should be better. And the third approach, that is used mostly in real world, I think - using ThreadLocal. I decided to test all these approaches. The result of my test is on the graph:
On the vertical axis here is the time, so the less - the better. On the horizontal axis is number of threads concurrently formatting dates. So what can be concluded from this graph?

  • Either SimpleDateFormat is not so heavyweight object, either synchronization is so heavy operation (I incline to the second option), but there is very little difference in synchronizing on one and the same instance of creating new one each time it is needed. However synchronizing is a bit better.
  • Widely used approach among the naive is the best.
EDIT: Doing this test I used Java6 from Sun. I have retested it with Java7 from Oracle and get quite different picture. The hardware is different too, so these pictures can not be compared with each other in absolute numbers. But difference in the relations between "new instance each invocation" approach and one synchronized instance is very interesting:

Anyway, here is the code I used for testing:


package com.sopovs.moradanen;

import java.util.Date;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import java.text.SimpleDateFormat;

public class ThreadLocalVsSynchronizedTest {
    private static final int ITERATIONS = 10000;
    private static final int NUM_THREADS = 15;
    private static final int BOOTSTRAP = 30;

    public static void main(String[] args) throws InterruptedException {
        //warming up
        test(new ThreadLocalFormatter(), 10);
        test(new SynchronizedFormatter(), 10);
        test(new NewInstanceFormatter(), 10);

        for (int i = 1; i <= NUM_THREADS; i++) {
            long threadLocalTime = 0, synchronizedTime = 0, newInstanceTime = 0;
            for (int j = 0; j < BOOTSTRAP; j++) {
                synchronizedTime += test(new SynchronizedFormatter(), i);
                threadLocalTime += test(new ThreadLocalFormatter(), i);
                newInstanceTime += test(new NewInstanceFormatter(), i);
            }
            System.out.print(i + " "
                    + TimeUnit.MILLISECONDS.convert(threadLocalTime, TimeUnit.NANOSECONDS));
            System.out.print(" "
                    + TimeUnit.MILLISECONDS.convert(synchronizedTime, TimeUnit.NANOSECONDS));
            System.out.println(" "
                    + TimeUnit.MILLISECONDS.convert(newInstanceTime, TimeUnit.NANOSECONDS));
        }
    }

    private static long test(Formatter formatter, int numThreads) throws InterruptedException {
        long start = System.nanoTime();
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        Executors.newFixedThreadPool(numThreads);

        for (int i = 0; i < numThreads; i++) {
            executor.execute(new FormatterWorker(formatter));
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.DAYS);

        return System.nanoTime() - start;
    }

    private static class FormatterWorker implements Runnable {
        private final Formatter formatter;

        public FormatterWorker(Formatter formatter) {
            this.formatter = formatter;
        }

        @Override
        public void run() {
            Random r = new Random();
            for (int i = 0; i < ITERATIONS; i++) {
                formatter.format(new Date(r.nextLong()));
            }
        }

    }

    private interface Formatter {
        String format(Date date);
    }

    private static class SynchronizedFormatter implements Formatter {
        private final SimpleDateFormat format = new SimpleDateFormat();

        @Override
        public synchronized String format(Date date) {
            return format.format(date);
        }
    }

    private static class ThreadLocalFormatter implements Formatter {
        private final ThreadLocal<SimpleDateFormat> format = new ThreadLocal<SimpleDateFormat>();

        @Override
        public String format(Date date) {
            SimpleDateFormat dateFormat = this.format.get();
            if (dateFormat == null) {
                dateFormat = new SimpleDateFormat();
                format.set(dateFormat);
            }
            return dateFormat.format(date);
        }
    }

    private static class NewInstanceFormatter implements Formatter {
        @Override
        public String format(Date date) {
            return new SimpleDateFormat().format(date);
        }
    }
}

2 comments:

  1. Apache Commons Lang 3.x now has FastDateParser and FastDateFormat. These classes are thread safe and faster than SimpleDateFormat. They also support the same format/parse pattern specifications as SimpleDateFormat.

    ReplyDelete