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