Skip to content

Commit 6ba7f5e

Browse files
author
Richard Jones
committed
Add additional unit tests to show that singletons can be created in single thread environment and multithread environment. Also add a test to demonstrate a whole with Singleton when instantiating using reflection
1 parent 0a9879a commit 6ba7f5e

File tree

1 file changed

+100
-64
lines changed

1 file changed

+100
-64
lines changed

singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java

Lines changed: 100 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,109 @@
22

33
import org.junit.Test;
44

5+
import java.lang.reflect.Constructor;
6+
import java.lang.reflect.Field;
7+
import java.lang.reflect.InvocationTargetException;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.concurrent.*;
11+
12+
import static org.junit.Assert.assertEquals;
13+
import static org.junit.Assert.assertNull;
14+
515
/**
6-
*
7-
* This test case demonstrates thread safety issues of lazy loaded Singleton implementation.
8-
* <p>
9-
* Out of the box you should see the test output something like the following:
10-
* <p>
11-
* Thread=Thread-4 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e
12-
* Thread=Thread-2 creating instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e
13-
* Thread=Thread-0 creating instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e
14-
* Thread=Thread-0 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e
15-
* Thread=Thread-3 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e
16-
* Thread=Thread-1 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@60f330b0
17-
* Thread=Thread-2 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e
18-
* <p>
19-
* By changing the method signature of LazyLoadedIvoryTower#getInstance from
20-
* <p><blockquote><pre>
21-
* public static LazyLoadedIvoryTower getInstance()
22-
* </pre></blockquote><p>
23-
* into
24-
* <p><blockquote><pre>
25-
* public synchronized static LazyLoadedIvoryTower getInstance()
26-
* </pre></blockquote><p>
27-
* you should see the test output change to something like the following:
28-
* <p>
29-
* Thread=Thread-4 creating instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490
30-
* Thread=Thread-4 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490
31-
* Thread=Thread-0 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490
32-
* Thread=Thread-3 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490
33-
* Thread=Thread-2 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490
34-
* Thread=Thread-1 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490
16+
* This class provides several test case that test singleton construction.
17+
*
18+
* The first proves that multiple calls to the singleton getInstance object are the same when called in the SAME thread.
19+
* The second proves that multiple calls to the singleton getInstance object are the same when called in the DIFFERENT thread.
20+
* The third proves that there is a hole if the singleton is created reflectively
3521
*
3622
*/
3723
public class LazyLoadedSingletonThreadSafetyTest {
3824

39-
private static final int NUM_THREADS = 5;
40-
41-
@Test
42-
public void test() {
43-
SingletonThread runnable = new SingletonThread();
44-
for (int j=0; j<NUM_THREADS; j++) {
45-
Thread thread = new Thread(runnable);
46-
thread.start();
47-
}
48-
}
49-
50-
private static class SingletonThread implements Runnable {
51-
52-
@Override
53-
public void run() {
54-
LazyLoadedIvoryTower instance = LazyLoadedIvoryTower.getInstance();
55-
System.out.println("Thread=" + Thread.currentThread().getName() + " got instance=" + instance);
56-
}
57-
}
58-
59-
private static class LazyLoadedIvoryTower {
60-
61-
private static LazyLoadedIvoryTower instance = null;
62-
63-
private LazyLoadedIvoryTower() {
64-
}
65-
66-
public static LazyLoadedIvoryTower getInstance() {
67-
if (instance == null) {
68-
instance = new LazyLoadedIvoryTower();
69-
System.out.println("Thread=" + Thread.currentThread().getName() + " creating instance=" + instance);
70-
}
71-
return instance;
72-
}
73-
}
25+
private static final int NUM_THREADS = 5;
26+
private List<ThreadSafeLazyLoadedIvoryTower> threadObjects = new ArrayList<>();
27+
28+
//NullObject class so Callable has to return something
29+
private class NullObject{private NullObject(){}}
30+
31+
@Test
32+
public void test_MultipleCallsReturnTheSameObjectInSameThread() {
33+
//Create several instances in the same calling thread
34+
ThreadSafeLazyLoadedIvoryTower instance1 = ThreadSafeLazyLoadedIvoryTower.getInstance();
35+
ThreadSafeLazyLoadedIvoryTower instance2 = ThreadSafeLazyLoadedIvoryTower.getInstance();
36+
ThreadSafeLazyLoadedIvoryTower instance3 = ThreadSafeLazyLoadedIvoryTower.getInstance();
37+
//now check they are equal
38+
assertEquals(instance1, instance1);
39+
assertEquals(instance1, instance2);
40+
assertEquals(instance2, instance3);
41+
assertEquals(instance1, instance3);
42+
}
43+
44+
@Test
45+
public void test_MultipleCallsReturnTheSameObjectInDifferentThreads() throws InterruptedException, ExecutionException {
46+
{//create several threads and inside each callable instantiate the singleton class
47+
ExecutorService executorService = Executors.newSingleThreadExecutor();
48+
49+
List<Callable<NullObject>> threadList = new ArrayList<>();
50+
for (int i = 0; i < NUM_THREADS; i++) {
51+
threadList.add(new SingletonCreatingThread());
52+
}
53+
54+
ExecutorService service = Executors.newCachedThreadPool();
55+
List<Future<NullObject>> results = service.invokeAll(threadList);
56+
57+
//wait for all of the threads to complete
58+
for (Future res : results) {
59+
res.get();
60+
}
61+
62+
//tidy up the executor
63+
executorService.shutdown();
64+
}
65+
{//now check the contents that were added to threadObjects by each thread
66+
assertEquals(NUM_THREADS, threadObjects.size());
67+
assertEquals(threadObjects.get(0), threadObjects.get(1));
68+
assertEquals(threadObjects.get(1), threadObjects.get(2));
69+
assertEquals(threadObjects.get(2), threadObjects.get(3));
70+
assertEquals(threadObjects.get(3), threadObjects.get(4));
71+
}
72+
}
73+
74+
@Test
75+
@SuppressWarnings("unchecked")
76+
public void test_HoleInSingletonCreationIfUsingReflection() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
77+
Field[] f = ThreadSafeLazyLoadedIvoryTower.class.getDeclaredFields();
78+
assertEquals("One field only in ThreadSafeLazyLoadedIvoryTower", 1, f.length);
79+
f[0].setAccessible(true);
80+
81+
{//reflectively create an object - the singleton field is null
82+
Class lazyIvoryTowerClazz = Class.forName("com.iluwatar.singleton.ThreadSafeLazyLoadedIvoryTower");
83+
Constructor<ThreadSafeLazyLoadedIvoryTower> constructor = lazyIvoryTowerClazz.getDeclaredConstructor();
84+
constructor.setAccessible(true);
85+
ThreadSafeLazyLoadedIvoryTower instance = constructor.newInstance();
86+
assertNull(f[0].get(instance));
87+
}
88+
89+
//instantiate the singleton but when we do the below code we are creating a new object where it is set to null still
90+
IvoryTower.getInstance();
91+
92+
{//reflectively create an object - the singleton field is null as a new object is created
93+
Class lazyIvoryTowerClazz = Class.forName("com.iluwatar.singleton.ThreadSafeLazyLoadedIvoryTower");
94+
Constructor<ThreadSafeLazyLoadedIvoryTower> constructor = lazyIvoryTowerClazz.getDeclaredConstructor();
95+
constructor.setAccessible(true);
96+
ThreadSafeLazyLoadedIvoryTower instance = constructor.newInstance();
97+
assertNull(f[0].get(instance));
98+
}
99+
}
100+
101+
private class SingletonCreatingThread implements Callable<NullObject> {
102+
@Override
103+
public NullObject call() {
104+
//instantiate the thread safety class and add to list to test afterwards
105+
ThreadSafeLazyLoadedIvoryTower instance = ThreadSafeLazyLoadedIvoryTower.getInstance();
106+
threadObjects.add(instance);
107+
return new NullObject();//return null object (cannot return Void)
108+
}
109+
}
74110
}

0 commit comments

Comments
 (0)