Friday, April 06, 2007

Calendar, DateFormat and multi threading

Another anecdote from the ongoing Java 1.4 to Java 5 transition... Last time it was an incarnation of "Favor composition over inheritance". This time I am writing about a multi-threading issue which can be equally subtle and difficult to pinpoint.

On the test systems we have set up we observed two strange types of exceptions at seemingly random times and in various different areas of the application. They looked similar to the following two patterns:

  • java.lang.ArrayIndexOutOfBoundsException: 432
            at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436)
            at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2024)
            at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:1941)
            at java.util.Calendar.setTimeInMillis(Calendar.java:1066)
            at java.util.Calendar.setTime(Calendar.java:1032)
            ... (our classes)
    
  • java.lang.ClassCastException: java.util.SimpleTimeZone
     at java.util.Calendar.writeObject(Calendar.java:1643)
     at sun.reflect.GeneratedMethodAccessor500.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
     at java.lang.reflect.Method.invoke(Method.java:324)
     at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:795)
     at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1294)
     at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1245)
     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1052)
     at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1330)
     at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1302)
     at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1245)
     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1052)
     at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1330)
     ... (more of this)
     ... (our classes)
    

Those errors, especially the first one, would suddenly happen at a seemingly random place. Re-running the program with the same inputs on the same machine would sometimes complete normally and sometimes crash at a different place.

Searching for the sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:436) line brought me to Java Bug #6231579 which describes exactly our problem. The bug has been closed and classified as "not a bug", because they say that Calendar was not designed to be thread-safe and that suchlike behavior can be expected when sharing an instance over several threads. This is exactly what we found in our product: In a utility class we used two static final java.util.Calendar instances that were shared among all threads using the utility. Interestingly Calendar's javadoc does not mention the unsafe nature of the Calendar class. Alright, thinking about it in retrospective sharing them was not such a good idea after all, because one could have guessed that there might be problems. So changing this piece of code was rather easy, once it had been found.

However SimpleDateFormat's documentation includes a hint about synchronization issues. This one is even more important, because there are probably many more people using some shared instances of those and get bitten. There is a series of bugs reported about this, one of them being Bug #4264153.

So maybe it is time to search your code for all static usages of the Calendar and various ...Format classes, before you start getting strange errors.

1 comment:

Javin said...

Thanks for your comment on my post SimpleDateFormat in Java is not Thread-Safe Use Carefully. I see you have covered it 5 years ago :) thanks for your find bugs contribution you mentioned, gonna try it.