diff --git a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java b/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java index ca88f4169..36d04f95f 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java @@ -2,73 +2,109 @@ package com.iluwatar.singleton; import org.junit.Test; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + /** - * - * This test case demonstrates thread safety issues of lazy loaded Singleton implementation. - *

- * Out of the box you should see the test output something like the following: - *

- * Thread=Thread-4 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e - * Thread=Thread-2 creating instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e - * Thread=Thread-0 creating instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e - * Thread=Thread-0 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e - * Thread=Thread-3 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e - * Thread=Thread-1 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@60f330b0 - * Thread=Thread-2 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@6fde356e - *

- * By changing the method signature of LazyLoadedIvoryTower#getInstance from - *

- * 	 public static LazyLoadedIvoryTower getInstance()
- * 

- * into - *

- *   public synchronized static LazyLoadedIvoryTower getInstance()
- * 

- * you should see the test output change to something like the following: - *

- * Thread=Thread-4 creating instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490 - * Thread=Thread-4 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490 - * Thread=Thread-0 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490 - * Thread=Thread-3 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490 - * Thread=Thread-2 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490 - * Thread=Thread-1 got instance=com.iluwatar.singleton.LazyLoadedSingletonThreadSafetyTest$LazyLoadedIvoryTower@3c688490 + * This class provides several test case that test singleton construction. + * + * The first proves that multiple calls to the singleton getInstance object are the same when called in the SAME thread. + * The second proves that multiple calls to the singleton getInstance object are the same when called in the DIFFERENT thread. + * The third proves that there is a hole if the singleton is created reflectively * */ public class LazyLoadedSingletonThreadSafetyTest { - private static final int NUM_THREADS = 5; - - @Test - public void test() { - SingletonThread runnable = new SingletonThread(); - for (int j=0; j threadObjects = new ArrayList<>(); - @Override - public void run() { - LazyLoadedIvoryTower instance = LazyLoadedIvoryTower.getInstance(); - System.out.println("Thread=" + Thread.currentThread().getName() + " got instance=" + instance); - } - } - - private static class LazyLoadedIvoryTower { + //NullObject class so Callable has to return something + private class NullObject{private NullObject(){}} - private static LazyLoadedIvoryTower instance = null; - - private LazyLoadedIvoryTower() { - } + @Test + public void test_MultipleCallsReturnTheSameObjectInSameThread() { + //Create several instances in the same calling thread + ThreadSafeLazyLoadedIvoryTower instance1 = ThreadSafeLazyLoadedIvoryTower.getInstance(); + ThreadSafeLazyLoadedIvoryTower instance2 = ThreadSafeLazyLoadedIvoryTower.getInstance(); + ThreadSafeLazyLoadedIvoryTower instance3 = ThreadSafeLazyLoadedIvoryTower.getInstance(); + //now check they are equal + assertEquals(instance1, instance1); + assertEquals(instance1, instance2); + assertEquals(instance2, instance3); + assertEquals(instance1, instance3); + } - public static LazyLoadedIvoryTower getInstance() { - if (instance == null) { - instance = new LazyLoadedIvoryTower(); - System.out.println("Thread=" + Thread.currentThread().getName() + " creating instance=" + instance); - } - return instance; - } - } + @Test + public void test_MultipleCallsReturnTheSameObjectInDifferentThreads() throws InterruptedException, ExecutionException { + {//create several threads and inside each callable instantiate the singleton class + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + List> threadList = new ArrayList<>(); + for (int i = 0; i < NUM_THREADS; i++) { + threadList.add(new SingletonCreatingThread()); + } + + ExecutorService service = Executors.newCachedThreadPool(); + List> results = service.invokeAll(threadList); + + //wait for all of the threads to complete + for (Future res : results) { + res.get(); + } + + //tidy up the executor + executorService.shutdown(); + } + {//now check the contents that were added to threadObjects by each thread + assertEquals(NUM_THREADS, threadObjects.size()); + assertEquals(threadObjects.get(0), threadObjects.get(1)); + assertEquals(threadObjects.get(1), threadObjects.get(2)); + assertEquals(threadObjects.get(2), threadObjects.get(3)); + assertEquals(threadObjects.get(3), threadObjects.get(4)); + } + } + + @Test + @SuppressWarnings("unchecked") + public void test_HoleInSingletonCreationIfUsingReflection() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + Field[] f = ThreadSafeLazyLoadedIvoryTower.class.getDeclaredFields(); + assertEquals("One field only in ThreadSafeLazyLoadedIvoryTower", 1, f.length); + f[0].setAccessible(true); + + {//reflectively create an object - the singleton field is null + Class lazyIvoryTowerClazz = Class.forName("com.iluwatar.singleton.ThreadSafeLazyLoadedIvoryTower"); + Constructor constructor = lazyIvoryTowerClazz.getDeclaredConstructor(); + constructor.setAccessible(true); + ThreadSafeLazyLoadedIvoryTower instance = constructor.newInstance(); + assertNull(f[0].get(instance)); + } + + //instantiate the singleton but when we do the below code we are creating a new object where it is set to null still + IvoryTower.getInstance(); + + {//reflectively create an object - the singleton field is null as a new object is created + Class lazyIvoryTowerClazz = Class.forName("com.iluwatar.singleton.ThreadSafeLazyLoadedIvoryTower"); + Constructor constructor = lazyIvoryTowerClazz.getDeclaredConstructor(); + constructor.setAccessible(true); + ThreadSafeLazyLoadedIvoryTower instance = constructor.newInstance(); + assertNull(f[0].get(instance)); + } + } + + private class SingletonCreatingThread implements Callable { + @Override + public NullObject call() { + //instantiate the thread safety class and add to list to test afterwards + ThreadSafeLazyLoadedIvoryTower instance = ThreadSafeLazyLoadedIvoryTower.getInstance(); + threadObjects.add(instance); + return new NullObject();//return null object (cannot return Void) + } + } }