/*
More tests of various singleton implementations
last update: Mon Mar 26 20:19:39 2001 Doug Lea (dl at gee)
*/
class TSS extends Thread {
static abstract class Singleton {
// a field and method to prevent some compiler optimizations
int aField = System.identityHashCode(this);
int aMethod(int i) { return (i % 17) != 0 ? aField: i; }
}
static class EagerSingleton extends Singleton {
static final EagerSingleton theInstance = new EagerSingleton();
static EagerSingleton getInstance() {
return theInstance;
}
}
static class SynchedSingleton extends Singleton {
static SynchedSingleton theInstance;
static synchronized SynchedSingleton getInstance() {
if (theInstance == null)
theInstance = new SynchedSingleton();
return theInstance;
}
}
static class ThreadLocalSingleton extends Singleton {
static final ThreadLocal perThreadInstance = new ThreadLocal();
static final Object lock = new Object();
static ThreadLocalSingleton theInstance;
static ThreadLocalSingleton getInstance() {
ThreadLocalSingleton instance = (ThreadLocalSingleton)(perThreadInstance.get());
if (instance == null) {
synchronized(lock) {
instance = theInstance;
if (instance == null)
instance = theInstance = new ThreadLocalSingleton();
}
// copy global to per-thread
perThreadInstance.set(instance);
}
return instance;
}
}
static class SimulatedThreadLocalSingleton extends Singleton {
static SimulatedThreadLocalSingleton theInstance;
static final Object lock = new Object();
static final Object key = new Object();
static Singleton getInstance() {
TSS t = (TSS)(Thread.currentThread());
Singleton instance = (Singleton)(t.threadLocalHashtable.get(key));
if (instance == null) {
synchronized(lock) {
instance = theInstance;
if (instance == null)
instance = theInstance = new SimulatedThreadLocalSingleton();
}
// copy global to per-thread
t.threadLocalHashtable.put(key, instance);
}
return instance;
}
}
static class VolatileSingleton extends Singleton {
static final Object lock = new Object();
static volatile VolatileSingleton theInstance;
static VolatileSingleton getInstance() {
VolatileSingleton instance = theInstance;
if (instance == null) {
synchronized(lock) {
instance = theInstance;
if (instance == null)
instance = theInstance = new VolatileSingleton();
}
}
return instance;
}
}
static class DirectThreadFieldSingleton extends Singleton {
static DirectThreadFieldSingleton theInstance;
static final Object lock = new Object();
static Singleton getInstance(TSS t) {
Singleton instance = t.singleton;
if (instance == null) {
synchronized(lock) {
instance = theInstance;
if (instance == null)
instance = theInstance = new DirectThreadFieldSingleton();
}
// copy global to per-thread
t.singleton = instance;
}
return instance;
}
}
static class ThreadFieldSingleton extends Singleton {
static final Object lock = new Object();
static ThreadFieldSingleton theInstance;
static Singleton getInstance() {
TSS t = (TSS)(Thread.currentThread());
Singleton instance = t.singleton;
if (instance == null) {
synchronized(lock) {
instance = theInstance;
if (instance == null)
instance = theInstance = new ThreadFieldSingleton();
}
// copy global to per-thread
t.singleton = instance;
}
return instance;
}
}
static final int ITERS = 1000000;
static final int NTHREADS = 8;
static int total; // accumulate calls to aMethod, to prevent overoptimizing
final IDHashMap threadLocalHashtable = new IDHashMap(8);
Singleton singleton;
int mode;
TSS(int md) { mode = md; }
public void run() {
int sum = 0; // to prevent optimizations
if (mode == 0) {
for (int i = 0; i < ITERS; ++i) {
sum += EagerSingleton.getInstance().aMethod(i);
}
}
else if (mode == 1) {
for (int i = 0; i < ITERS; ++i) {
sum += ThreadLocalSingleton.getInstance().aMethod(i);
}
}
else if (mode == 2) {
for (int i = 0; i < ITERS; ++i) {
sum += SimulatedThreadLocalSingleton.getInstance().aMethod(i);
}
}
else if (mode == 3) {
for (int i = 0; i < ITERS; ++i) {
sum += VolatileSingleton.getInstance().aMethod(i);
}
}
else if (mode == 4) {
for (int i = 0; i < ITERS; ++i) {
sum += SynchedSingleton.getInstance().aMethod(i);
}
}
else if (mode == 5) {
for (int i = 0; i < ITERS; ++i) {
sum += DirectThreadFieldSingleton.getInstance(this).aMethod(i);
}
}
else if (mode == 6) {
for (int i = 0; i < ITERS; ++i) {
sum += ThreadFieldSingleton.getInstance().aMethod(i);
}
}
total += sum;
}
public static void main(String[] args) {
Thread[] threads = new Thread[NTHREADS];
for (int reps = 0; reps < 3; ++reps) {
for (int i = 0; i < NTHREADS; ++i)
threads[i] = null;
System.gc();
for (int mode = 0; mode < 7; ++mode) {
if (mode == 0)
System.out.print("Eager: ");
else if (mode == 1)
System.out.print("ThreadLocal: ");
else if (mode == 2)
System.out.print("SimThreadLocal: ");
else if (mode == 3)
System.out.print("Volatile (DCL): ");
else if (mode == 4)
System.out.print("Synch: ");
else if (mode == 5)
System.out.print("Direct Field: ");
else if (mode == 6)
System.out.print("Thread Field: ");
long startTime = System.currentTimeMillis();
for (int i = 0; i < NTHREADS; ++i)
threads[i] = new TSS(mode);
for (int i = 0; i < NTHREADS; ++i)
threads[i].start();
try {
for (int i = 0; i < NTHREADS; ++i)
threads[i].join();
}
catch (InterruptedException ie) {
System.out.println("Interrupted");
return;
}
long elapsed = System.currentTimeMillis() - startTime;
System.out.println(elapsed + "ms");
if (total == 0) // ensure total is live to avoid optimizing away
System.out.println("useless number = " + total);
}
}
}
}
/*
Renamed and hacked version of 1.4 IdentityHashMap so can test on pre-1.4
*/
class IDHashMap {
/**
* The initial capacity used by the no-args constructor.
* MUST be a power of two. The value 32 corresponds to the
* (specified) expected maximum size of 21, given a load factor
* of 2/3.
*/
private static final int DEFAULT_CAPACITY = 32;
/**
* The minimum capacity, used if a lower value is implicitly specified
* by either of the constructors with arguments. The value 4 corresponds
* to an expected maximum size of 2, given a load factor of 2/3.
* MUST be a power of two.
*/
private static final int MINIMUM_CAPACITY = 4;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<29.
*/
private static final int MAXIMUM_CAPACITY = 1 << 29;
/**
* Special value used to mark slots as deleted.
*/
private static final Object DELETED = new Object();
/**
* Special value used to mark slots as empty.
*/
private static final Object EMPTY = new Object();
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
private transient Object[] table;
/**
* The number of key-value mappings contained in this identity hash map.
*
* @serial
*/
private int size;
/**
* The next size value at which to resize (capacity * load factor).
*/
private transient int threshold;
/**
* Constructs a new, empty identity hash map with a default expected
* maximum size (21).
*/
public IDHashMap() {
init(DEFAULT_CAPACITY);
}
/**
* Constructs a new, empty map with the specified expected maximum size.
* Putting more than the expected number of key-value mappings into
* the map may cause the internal data structure to grow, which may be
* somewhat time-consuming.
*
* @param expectedMaxSize the expected maximum size of the map.
* @throws IllegalArgumentException if expectedMaxSize is negative
*/
public IDHashMap(int expectedMaxSize) {
if (expectedMaxSize < 0)
throw new IllegalArgumentException("expectedMaxSize is negative");
init(capacity(expectedMaxSize));
}
/**
* Returns the appropriate capacity for the specified expected maximum
* size. Returns the smallest power of two between MINIMUM_CAPACITY
* and MAXIMUM_CAPACITY, inclusive, that is greater than
* (3 * expectedMaxSize)/2, if such a number exists. Otherwise
* returns MAXIMUM_CAPACITY. If (3 * expectedMaxSize)/2 is negative, it
* is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned.
*/
private int capacity(int expectedMaxSize) {
// Compute min capacity for expectedMaxSize given a load factor of 2/3
int minCapacity = (3 * expectedMaxSize)/2;
// Compute the appropriate capacity
int result;
if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) {
result = MAXIMUM_CAPACITY;
} else {
result = MINIMUM_CAPACITY;
while (result < minCapacity)
result <<= 1;
}
return result;
}
/**
* Initialize object to be an empty map with the specified initial
* capacity, which is assumed to be a power of two between
* MINIMUM_CAPACITY and MAXIMUM_CAPACITY inclusive.
*/
private void init(int initCapacity) {
// assert (initCapacity & -initCapacity) == initCapacity; // power of 2
// assert initCapacity >= MINIMUM_CAPACITY;
// assert initCapacity <= MAXIMUM_CAPACITY;
threshold = (initCapacity * 2)/3;
table = new Object[2 * initCapacity];
for (int i = 0; i < table.length; i += 2)
table[i] = EMPTY;
}
/**
* Returns the number of key-value mappings in this identity hash map.
*
* @return the number of key-value mappings in this map.
*/
public int size() {
return size;
}
/**
* Returns true if this identity hash map contains no key-value
* mappings.
*
* @return true if this identity hash map contains no key-value
* mappings.
*/
public boolean isEmpty() {
return size == 0;
}
/**
* Return index for Object x given table size len, where len is a power of
* two.
*/
private static int hash(Object x, int len) {
int h = System.identityHashCode(x);
return h & (len-2);
}
/**
* Returns the value to which the specified key is mapped in this identity
* hash map, or null if the map contains no mapping for
* this key. A return value of null does not necessarily
* indicate that the map contains no mapping for the key; it is also
* possible that the map explicitly maps the key to null. The
* containsKey method may be used to distinguish these two
* cases.
*
* @param key the key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or
* null if the map contains no mapping for this key.
* @see #put(Object, Object)
*/
public Object get(Object key) {
int i = hash(key, table.length);
while (true) {
Object item = table[i];
if (item == key)
return table[i+1];
if (item == EMPTY)
return null;
if ((i+=2) >= table.length)
i = 0;
}
}
/**
* Associates the specified value with the specified key in this identity
* hash map. If the map previously contained a mapping for this key, the
* old value is replaced.
*
* @param key the key with which the specified value is to be associated.
* @param value the value to be associated with the specified key.
* @return the previous value associated with key, or
* null if there was no mapping for key. (A
* null return can also indicate that the map previously
* associated null with the specified key.)
* @see Object#equals(Object)
* @see #get(Object)
* @see #containsKey(Object)
*/
public Object put(Object key, Object value) {
/*
* insertionIndex is the index of the first DELETED
* entry passed over while checking if x is already
* present. If such a slot exists, we should use it
* rather than trailing null slot
*/
int insertionIndex = -1;
int i = hash(key, table.length);
while (true) {
Object item = table[i];
if (item == EMPTY) {
if (insertionIndex < 0)
insertionIndex = i;
table[insertionIndex] = key;
table[insertionIndex+1] = value;
if (++size >= threshold) resize();
return null;
} else if (item == key) {
Object oldValue = table[++i];
table[i] = value;
return oldValue;
} else if (item == DELETED && insertionIndex < 0) {
insertionIndex = i;
}
if ((i+=2) >= table.length)
i = 0;
}
}
/**
* Double the size of the table
*/
private void resize() {
int oldTableSize = table.length;
if (oldTableSize == 2*MAXIMUM_CAPACITY) { // can't expand any further
if (threshold == MAXIMUM_CAPACITY-1)
throw new IllegalStateException("Capacity exhausted.");
threshold = MAXIMUM_CAPACITY-1; // Gigantic map!
return;
}
int newSize = 2 * oldTableSize;
threshold = (oldTableSize * 2)/3;
Object[] oldTable = table;
table = new Object[newSize];
for (int i = 0; i < table.length; i +=2)
table[i] = EMPTY;
for (int j = 0; j < oldTable.length; j+=2) {
Object key = oldTable[j];
if (key != EMPTY && key != DELETED) {
Object value = oldTable[j+1];
int i = hash(key, table.length);
while (table[i] != EMPTY) {
if ((i+=2) == table.length)
i = 0;
}
table[i] = key;
table[i+1] = value;
}
}
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or null
* if there was no entry for key. (A null return can
* also indicate that the map previously associated null
* with the specified key.)
*/
public Object remove(Object key) {
int i = hash(key, table.length);
while (true) {
Object item = table[i];
if (item == EMPTY)
return null;
else if (item == key) {
Object oldValue = table[i+1];
markAsDeleted(i);
return oldValue;
}
if ((i+=2) >= table.length)
i = 0;
}
}
/**
* Mark table[index] as either DELETED or, if possible, EMPTY.
*/
private void markAsDeleted(int index) {
/*
* Because table[index] could have been between two real items
* without an intervening EMPTY, it must normally be marked as
* DELETED so that linear probing continues to work. But if it is
* part of a sequence of DELETEDs ending in a EMPTY, table[index] and
* other members of the sequence can be set to EMPTY, which will
* shorten subsequent searches, especially after bursts of
* removals. It also guarantees that an empty table has no DELETED
* markers. In practice, this keeps the number of DELETED markers
* low enough to not hurt search times much in the presence of
* deletions.
*
* Note that the alternative of re-inserting old elements rather
* than using a DELETED marker cannot be used here because this
* could re-arrange items in the midst of an iteration.
*/
--size;
table[index+1] = null; // null out value;
int j = index;
while (true) { // Traverse starting at next slot after index
if ((j+=2) == table.length)
j = 0;
Object item = table[j];
if (item == EMPTY) // Found a sequence ending in EMPTY
break;
else if (item != DELETED) { // no such luck
table[index] = DELETED;
return;
}
}
while (true) { // Run backwards until not DELETED
if ((j-=2) < 0)
j = table.length - 2;
if (j == index || table[j] == DELETED)
table[j] = EMPTY;
else
return;
}
}
/**
* Removes all mappings from this map.
*/
public void clear() {
for (int i = 0; i < table.length; i+=2)
table[i] = EMPTY;
for (int i = 1; i < table.length; i+=2)
table[i] = null;
size = 0;
}
}