diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java new file mode 100644 index 000000000..f27ffc6a9 --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java @@ -0,0 +1,42 @@ +package com.iluwatar.lazy.loading; + +import org.junit.Test; + +import java.lang.reflect.Field; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Using reflection this test shows that the heavy field is not instantiated until the method getHeavy is called + * + * Created by jones on 11/10/2015. + */ +public class HolderThreadSafeTest { + + @Test + public void test() throws IllegalAccessException { + HolderThreadSafe hts = new HolderThreadSafe(); + + {//first call is null + Field[] ff = HolderThreadSafe.class.getDeclaredFields(); + for (Field f: ff) { + f.setAccessible(true); + } + + assertNull(ff[0].get(hts)); + } + + // now it is lazily loaded + hts.getHeavy(); + + {//now it is not null - call via reflection so that the test is the same before and after + Field[] ff = HolderThreadSafe.class.getDeclaredFields(); + for (Field f: ff) { + f.setAccessible(true); + } + + assertNotNull(ff[0].get(hts)); + } + } +} diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java index f9b62e798..c50c99e65 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java @@ -4,6 +4,8 @@ package com.iluwatar.singleton; * Thread-safe Singleton class. * The instance is lazily initialized and thus needs synchronization * mechanism. + * + * Note: if created by reflection then a singleton will not be created but multiple options in the same classloader */ public class ThreadSafeLazyLoadedIvoryTower { diff --git a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java b/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java index ca88f4169..07f99005e 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java @@ -2,73 +2,78 @@ package com.iluwatar.singleton; import org.junit.Test; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.Assert.assertEquals; + /** - * - * 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. * */ public class LazyLoadedSingletonThreadSafetyTest { - private static final int NUM_THREADS = 5; - - @Test - public void test() { - SingletonThread runnable = new SingletonThread(); - for (int j=0; j threadObjects = Collections.synchronizedList(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)); + } + } + + 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) + } + } }