JDK-7017458 : (cal) Multithreaded deserialization of Calendar leads to ClassCastException
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 6u23,6u25
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: solaris_10,windows_vista
  • CPU: x86,sparc
  • Submitted: 2011-02-05
  • Updated: 2013-09-19
  • Resolved: 2012-02-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7 JDK 8
6-poolResolved 7u4Fixed 8 b25Fixed
Description
FULL PRODUCT VERSION :
java version "1.6.0_23"
Java(TM) SE Runtime Environment (build 1.6.0_23-b05)
Java HotSpot(TM) Client VM (build 19.0-b09, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [version 6.0.6002]

EXTRA RELEVANT SYSTEM CONFIGURATION :
Machine running with an Intel i7 920 processor (4 physical cores with hyperthreading = 8 virtual cores)

A DESCRIPTION OF THE PROBLEM :
Upon deserialization of java.util.Calendar instances by multiple threads, the following exception is raised:

java.lang.ClassCastException: java.util.SimpleTimeZone cannot be cast to sun.util.calendar.ZoneInfo
  at java.util.Calendar$1.run(Calendar.java:2653)
  at java.util.Calendar$1.run(Calendar.java:2651)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.util.Calendar.readObject(Calendar.java:2650)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  at java.lang.reflect.Method.invoke(Method.java:597)
  at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
  at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848)
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
  at CalendarSerialization$DeserializationThread.run(CalendarSerialization.java:80)

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- Save the attached code in CalendarSerialization.java
- compile with "javac CalendarSerialization.java"
- execute with "java -cp . CalendarSerialization."

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No error or exception should be displayed
ACTUAL -
One or more instances of "java.lang.ClassCastException: java.util.SimpleTimeZone cannot be cast to sun.util.calendar.ZoneInfo" are raised, with the corresponding stack trace

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.ClassCastException: java.util.SimpleTimeZone cannot be cast to sun.util.calendar.ZoneInfo
  at java.util.Calendar$1.run(Calendar.java:2653)
  at java.util.Calendar$1.run(Calendar.java:2651)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.util.Calendar.readObject(Calendar.java:2650)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  at java.lang.reflect.Method.invoke(Method.java:597)
  at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
  at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848)
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
  at CalendarSerialization$DeserializationThread.run(CalendarSerialization.java:80)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.util.Calendar;

/**
 * Test of multithreaded serialization/deserialization of Calendar.
 * @author Laurent Cohen
 */
public class CalendarSerialization
{
  public static void main(String[] args)
  {
    try
    {
      new CalendarSerialization().perform();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public void perform() throws Exception
  {
    int nbThreads = 8;
    Calendar cal = Calendar.getInstance();
    SerializationThread[] threads = new SerializationThread[nbThreads];
    for (int i=0; i<nbThreads; i++) threads[i] = new SerializationThread(cal);
    for (int i=0; i<nbThreads; i++) threads[i].start();
    for (int i=0; i<nbThreads; i++) threads[i].join();
    DeserializationThread[] threads2 = new DeserializationThread[nbThreads];
    for (int i=0; i<nbThreads; i++) threads2[i] = new DeserializationThread(threads[i].data);
    for (int i=0; i<nbThreads; i++) threads2[i].start();
    for (int i=0; i<nbThreads; i++) threads2[i].join();
  }

  public class SerializationThread extends Thread
  {
    private Calendar cal;
    public byte[] data;

    public SerializationThread(Calendar cal)
    {
      this.cal = cal;
    }

    public void run()
    {
      try
      {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(cal);
        oos.flush();
        oos.close();
        data = baos.toByteArray();
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
  }

  public class DeserializationThread extends Thread
  {
    public Calendar cal;
    public byte[] data;

    public DeserializationThread(byte[] data)
    {
      this.data = data;
    }

    public void run()
    {
      try
      {
        ByteArrayInputStream bais = new ByteArrayInputStream(data);
        ObjectInputStream ois = new ObjectInputStream(bais);
        cal = (Calendar) ois.readObject();
        ois.close();
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I do not know of any workaround

Comments
EVALUATION Calendar.writeObject will be made synchronized as a workaround fix. But in general Calendar is thread-unsafe even with only getter calls.
26-01-2012

WORK AROUND Add synchronization with `cal' in SerializationThread.run(), like this: ByteArrayOutputStream baos = new ByteArrayOutputStream(); synchronized (cal) { ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(cal); oos.flush(); oos.close(); } data = baos.toByteArray();
07-02-2011

EVALUATION Calendar isn't thread-safe by design. Any Calendar instances shouldn't be shared by threads.
07-02-2011