JavaMemoryModel: Problem on single processor

From: Paul Jakubik (pjakubik@dallas.objectspace.com)
Date: Wed May 24 2000 - 19:14:15 EDT


I was wanting to write some code to test multiprocessor safety, and wound up
finding issues on a single processor. I was testing the double-check locking
pattern and expected it to work on a single processor. I have been surprised
that sometimes I see it fail on a single processor. I have also been
surprised that this behavior is very inconsistent.

I am running on a Pentium II laptop with Windows NT 4.0 and service pack 5.
When I run java -version I get:

     java version "1.2.2"
     Classic VM (build JDK-1.2.2-001, native threads, symcjit)

In general when I run the program, I tell it to run with 2 threads and to
simulate instantiating 1,000,000 singletons. The option at the front sets
the maximum heap size to a larger than default value:

     java -Xmx80M DoubleCheckTest 2 1000000

When I run the program that follows, sometimes it works correctly giving the
output:

     waiting to join 0
     waiting to join 1
     done

And sometimes I get output like:

     waiting to join 0
     [636811] Singleton.a not initialized 0
     [636811] Singleton.b not intialized 0
     [636811] Singleton.c not intialized 0
     [636811] Singleton.dummy not initialized, value is null
     waiting to join 1
     done

or

     waiting to join 0
     [568293] Singleton.b not intialized 0
     [568293] Singleton.c not intialized 0
     [568293] Singleton.dummy not initialized, value is null
     [668849] Singleton.b not intialized 0
     [668849] Singleton.c not intialized 0
     [668849] Singleton.dummy not initialized, value is null
     [883501] Singleton.a not initialized 0
     [883501] Singleton.b not intialized 0
     [883501] Singleton.c not intialized 0
     [883501] Singleton.dummy not initialized, value is null
     waiting to join 1
     done

Anyone have thoughts on why double-check would fail on a single processor? I
have seen the same problem with the 1.1.8 JVM. I have not seen the problem
on the hot-spot or 1.3 JVMs. Finally, this behavior has been fairly machine
dependent. If someone could reproduce these results on other JVMs or
machines (especially single processors) I would like to hear about it.

--Paul

code:

public class DoubleCheckTest
  {
  

  // static data to aid in creating N singletons
  static final Object dummyObject = new Object(); // for reference init
  static final int A_VALUE = 256; // value to initialize 'a' to
  static final int B_VALUE = 512; // value to initialize 'b' to
  static final int C_VALUE = 1024;
  static ObjectHolder[] singletons; // array of static references
  static Thread[] threads; // array of racing threads
  static int threadCount; // number of threads to create
  static int singletonCount; // number of singletons to create
  

  // I am going to set a couple of threads racing,
  // trying to create N singletons. Basically the
  // race is to initialize a single array of
  // singleton references. The threads will use
  // double checked locking to control who
  // initializes what. Any thread that does not
  // initialize a particular singleton will check
  // to see if it sees a partially initialized view.
  // To keep from getting accidental synchronization,
  // each singleton is stored in an ObjectHolder
  // and the ObjectHolder is used for
  // synchronization. In the end the structure
  // is not exactly a singleton, but should be a
  // close enough approximation.
  //

  // This class contains data and simulates a
  // singleton. The static reference is stored in
  // a static array in DoubleCheckFail.
  static class Singleton
    {
    public int a;
    public int b;
    public int c;
    public Object dummy;

    public Singleton()
      {
      a = A_VALUE;
      b = B_VALUE;
      c = C_VALUE;
      dummy = dummyObject;
      }
    }

  static void checkSingleton(Singleton s, int index)
    {
    if(s.a != A_VALUE)
      System.out.println("[" + index + "] Singleton.a not initialized " +
s.a);

    if(s.b != B_VALUE)
      System.out.println("[" + index
                         + "] Singleton.b not intialized " + s.b);
    
    if(s.c != C_VALUE)
      System.out.println("[" + index
                         + "] Singleton.c not intialized " + s.c);
    
    if(s.dummy != dummyObject)
      if(s.dummy == null)
        System.out.println("[" + index
                           + "] Singleton.dummy not initialized,"
                           + " value is null");
      else
        System.out.println("[" + index
                           + "] Singleton.dummy not initialized,"
                           + " value is garbage");
    }

  // Holder used for synchronization of
  // singleton initialization.
  static class ObjectHolder
    {
    public Singleton reference;
    }

  static class TestThread implements Runnable
    {
    public void run()
      {
      for(int i = 0; i < singletonCount; ++i)
        {
        if(singletons[i].reference == null)
          {
          synchronized(singletons[i])
            {
            if (singletons[i].reference == null)
              singletons[i].reference = new Singleton();
            // shouldn't have to check singelton here
            // mutex should provide consistent view
            }
          }
        else
          checkSingleton(singletons[i].reference, i);
        }
      }
    }

  public static void main(String[] args)
    {
    if( args.length != 2 )
      {
      System.err.println("usage: java DoubleCheckFail" +
                         " <numThreads> <numSingletons>");
      }
    // read values from args
    threadCount = Integer.parseInt(args[0]);
    singletonCount = Integer.parseInt(args[1]);
    
    // create arrays
    threads = new Thread[threadCount];
    singletons = new ObjectHolder[singletonCount];

    // fill singleton array
    for(int i = 0; i < singletonCount; ++i)
      singletons[i] = new ObjectHolder();

    // fill thread array
    for(int i = 0; i < threadCount; ++i)
      threads[i] = new Thread( new TestThread() );

    // start threads
    for(int i = 0; i < threadCount; ++i)
      threads[i].start();

    // wait for threads to finish
    for(int i = 0; i < threadCount; ++i)
      {
      try
        {
        System.out.println("waiting to join " + i);
        threads[i].join();
        }
      catch(InterruptedException ex)
        {
        System.out.println("interrupted");
        }
      }
    System.out.println("done");
    }
  }
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:25 EDT