Friday, May 7, 2010

SimpleDateFormat and Thread Safety

SimpleDateFormat
The Java class SimpleDateFormat converts Dates to Strings and vice versa. For example, the following code converts the String "04/15/2010" to a Date object and back again:
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
Date d = df.parse("04/15/2010");
String s = df.format(d);
"04/15/2010".equals(s); //true
This class is documented as not being thread-safe, but I decided to see for myself if this was true.

Thread-safety proof
I created a program that proves that SimpleDateFormat is not thread-safe. It creates X threads which run concurrently. Each thread generates Y random Dates, then formats each Date in two ways: using a local instance of SimpleDateFormat that no other thread has access to, and using a static instance of SimpleDateFormat which all threads use. The Strings created by the local instance are added to one List and the Strings created by the static instance are added to another List.

If everything is synchronized properly, then these two lists should be identical. But because SimpleDateFormat is not thread safe and all threads use a shared static instance, the lists do not always come out identical (I've found that around ten threads and ten dates-per-thread consistently produce different lists).

If the call to "staticDf.format()" is wrapped in a synchronized block, then the lists always come out identical, which shows that SimpleDateFormat needs to be manually synchronized and is therefore not thread-safe.

Usage
The following command will create ten threads, each of which will generate twenty dates:
java SimpleDateFormatThreadSafe 10 20

Source code
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Proves that SimpleDateFormat is indeed not thread safe (as documented in the
 * javadocs).
 * @author mangstadt
 */
public class SimpleDateFormatThreadSafe {
  private static final String format = "MM/dd/yy";
  private static final DateFormat staticDf = new SimpleDateFormat(format);
  private static int numThreads = 10;
  private static int numLoopsPerThread = 10;

  public static void main(String args[]) throws Exception {
    //get the arguments
    if (args.length > 0){
      numThreads = Integer.parseInt(args[0]);
      if (args.length > 1){
        numLoopsPerThread = Integer.parseInt(args[1]);
      }
    }

    //create the threads
    MyThread threads[] = new MyThread[numThreads];
    for (int i = 0; i < threads.length; ++i) {
      threads[i] = new MyThread();
    }

    //start the threads
    for (MyThread t : threads) {
      t.start();
    }

    //check the results
    boolean allIdentical = true;
    for (MyThread t : threads) {
      t.join();
      if (!t.localList.equals(t.staticList)){
        System.out.println(t.getName() + " lists are different:");
        System.out.println("local:  " + t.localList);
        System.out.println("static: " + t.staticList);
        System.out.println();
        allIdentical = false;
      }
    }
    if (allIdentical){
      System.out.println("All lists are identical.");
    }
  }

  private static class MyThread extends Thread {
    public final List localList = new ArrayList();
    public final List staticList = new ArrayList();

    @Override
    public void run() {
      DateFormat localDf = new SimpleDateFormat(format);
      for (int i = 0; i < numLoopsPerThread; ++i) {
        //create a random Date
        Calendar c = Calendar.getInstance();
        c.set(Calendar.MONTH, randInt(0, 12));
        c.set(Calendar.DATE, randInt(1, 21));
        c.set(Calendar.YEAR, randInt(1990, 2011));
        Date d = c.getTime();

        //add formatted dates to lists
        localList.add(localDf.format(d));
        //synchronized (staticDf){
        staticList.add(staticDf.format(d));
        //}
      }
    }
  }

  private static int randInt(int min, int max) {
    return (int) (Math.random() * (max - min) + min);
  }
}

2 comments:

digital signatures said...

I am a tutor and was not able to think of any example by which i can prove that why this class is documented as not being thread-safe.Now i can give this example to them

Michael Angstadt said...

I'm glad that you found it useful.