diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingleton.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingleton.java deleted file mode 100644 index 86fc433..0000000 --- a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingleton.java +++ /dev/null @@ -1,22 +0,0 @@ -package pl.mperor.lab.java.design.pattern.creational.singleton; - -public class LazyInitializedSingleton { - - private static LazyInitializedSingleton instance; - private final long time = System.currentTimeMillis(); - - private LazyInitializedSingleton() { - } - - public static synchronized LazyInitializedSingleton getInstance() { - if (instance == null) { - instance = new LazyInitializedSingleton(); - } - return instance; - } - - public long getTime() { - return time; - } - -} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/Singleton.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/Singleton.java deleted file mode 100644 index 7f4c949..0000000 --- a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/Singleton.java +++ /dev/null @@ -1,19 +0,0 @@ -package pl.mperor.lab.java.design.pattern.creational.singleton; - -public class Singleton { - - private static final Singleton instance = new Singleton(); - private final long time = System.currentTimeMillis(); - - private Singleton() { - } - - public static Singleton getInstance() { - return instance; - } - - public long getTime() { - return time; - } - -} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonAtomic.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonAtomic.java new file mode 100644 index 0000000..776513a --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonAtomic.java @@ -0,0 +1,22 @@ +package pl.mperor.lab.java.design.pattern.creational.singleton; + +import java.util.concurrent.atomic.AtomicReference; + +public class SingletonAtomic implements SingletonInstance { + + private static final AtomicReference INSTANCE = new AtomicReference<>(); + private final long time = System.currentTimeMillis(); + + private SingletonAtomic() { + } + + public static SingletonAtomic getInstance() { + INSTANCE.compareAndSet(null, new SingletonAtomic()); + return INSTANCE.get(); + } + + @Override + public long getCreationTime() { + return time; + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonEager.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonEager.java new file mode 100644 index 0000000..21b7838 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonEager.java @@ -0,0 +1,19 @@ +package pl.mperor.lab.java.design.pattern.creational.singleton; + +public class SingletonEager implements SingletonInstance { + + private static final SingletonEager INSTANCE = new SingletonEager(); + private final long time = System.currentTimeMillis(); + + private SingletonEager() { + } + + public static SingletonEager getInstance() { + return INSTANCE; + } + + @Override + public long getCreationTime() { + return time; + } +} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonEnum.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonEnum.java new file mode 100644 index 0000000..f7fa5eb --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonEnum.java @@ -0,0 +1,12 @@ +package pl.mperor.lab.java.design.pattern.creational.singleton; + +public enum SingletonEnum implements SingletonInstance { + INSTANCE; + + private final long time = System.currentTimeMillis(); + + @Override + public long getCreationTime() { + return time; + } +} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonHolder.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonHolder.java new file mode 100644 index 0000000..de1b341 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonHolder.java @@ -0,0 +1,22 @@ +package pl.mperor.lab.java.design.pattern.creational.singleton; + +public class SingletonHolder implements SingletonInstance { + + private final long time = System.currentTimeMillis(); + + private SingletonHolder() { + } + + public static SingletonHolder getInstance() { + return Holder.INSTANCE; + } + + @Override + public long getCreationTime() { + return time; + } + + private static class Holder { + private static final SingletonHolder INSTANCE = new SingletonHolder(); + } +} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonInstance.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonInstance.java new file mode 100644 index 0000000..91d6a08 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonInstance.java @@ -0,0 +1,6 @@ +package pl.mperor.lab.java.design.pattern.creational.singleton; + +public interface SingletonInstance { + + long getCreationTime(); +} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonSerializable.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonSerializable.java new file mode 100644 index 0000000..92150b5 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonSerializable.java @@ -0,0 +1,31 @@ +package pl.mperor.lab.java.design.pattern.creational.singleton; + +import java.io.ObjectStreamException; +import java.io.Serial; +import java.io.Serializable; + +public class SingletonSerializable implements Serializable, SingletonInstance { + + @Serial + private static final long serialVersionUID = 1L; + + private static final SingletonSerializable INSTANCE = new SingletonSerializable(); + private final long time = System.currentTimeMillis(); + + private SingletonSerializable() { + } + + public static SingletonSerializable getInstance() { + return INSTANCE; + } + + @Override + public long getCreationTime() { + return time; + } + + // Prevent Serialization from creating a new instance + protected Object readResolve() throws ObjectStreamException { + return getInstance(); + } +} diff --git a/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingletonTest.java b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingletonTest.java deleted file mode 100644 index 192fbe2..0000000 --- a/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingletonTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package pl.mperor.lab.java.design.pattern.creational.singleton; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.concurrent.CompletableFuture; - -public class LazyInitializedSingletonTest { - - @Test - public void shouldOnlyAllowToLazyCreateOneInstanceOfSingleton() { - var first = CompletableFuture.supplyAsync(() -> { - System.out.println("First ..."); - return LazyInitializedSingleton.getInstance(); - }); - var second = CompletableFuture.supplyAsync(() -> { - System.out.println("Second ..."); - return LazyInitializedSingleton.getInstance(); - }); - - LazyInitializedSingleton firstResult = first.join(); - LazyInitializedSingleton secondResult = second.join(); - - Assertions.assertSame(firstResult, secondResult); - Assertions.assertEquals(firstResult.getTime(), secondResult.getTime()); - } -} \ No newline at end of file diff --git a/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonTest.java b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonTest.java index 061eeb3..324e6e7 100644 --- a/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonTest.java +++ b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonTest.java @@ -2,21 +2,105 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; -import java.util.concurrent.TimeUnit; +import java.io.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; +/** + * The purpose of this test is to randomly enable one of the test methods to execute. + * This is due to the fact that the tests involve the creation of a Singleton object. + * Since the Singleton is initialized only once, executing multiple tests could cause issues + * because subsequent tests would attempt to initialize the Singleton again, which is not allowed. + * By randomly selecting and enabling just one test per run, we avoid such conflicts and ensure + * the Singleton is properly handled without reinitialization. + */ public class SingletonTest { + private static int random = ThreadLocalRandom.current().nextInt(1, 4); + + static boolean isRandom1() { + return random == 1; + } + + static boolean isRandom2() { + return random == 2; + } + + static boolean isRandom3() { + return random == 3; + } + + @EnabledIf(value = "isRandom1") + @Test + public void testLazyInitialized() throws InterruptedException { + assertLazyInitialized(() -> SingletonEnum.INSTANCE); + assertLazyInitialized(SingletonEager::getInstance); + assertLazyInitialized(SingletonHolder::getInstance); + assertLazyInitialized(SingletonAtomic::getInstance); + assertLazyInitialized(SingletonSerializable::getInstance); + } + + private void assertLazyInitialized(Supplier supplier) throws InterruptedException { + long startedTime = System.currentTimeMillis(); + Thread.sleep(10); + var instance = supplier.get(); + long instanceTime = instance.getCreationTime(); + Assertions.assertTrue(startedTime < instanceTime); + } + + @EnabledIf(value = "isRandom2") @Test - public void shouldOnlyAllowToCreateOneInstanceOfSingleton() throws InterruptedException { - var instance = Singleton.getInstance(); - long time = instance.getTime(); + public void testSerializationSafe() throws IOException, ClassNotFoundException { + assertSerializationSafe(() -> SingletonEnum.INSTANCE); + assertSerializationSafe(SingletonSerializable::getInstance); + } + + private void assertSerializationSafe(Supplier supplier) throws IOException, ClassNotFoundException { + SingletonInstance instance = supplier.get(); - TimeUnit.MILLISECONDS.sleep(50); - long timeAfterBreak = Singleton.getInstance().getTime(); + var arrayOut = new ByteArrayOutputStream(); + try (var out = new ObjectOutputStream(arrayOut)) { + out.writeObject(instance); + } - Assertions.assertSame(instance, Singleton.getInstance()); - Assertions.assertEquals(time, timeAfterBreak); + SingletonInstance deserializedInstance; + try (var in = new ObjectInputStream(new ByteArrayInputStream(arrayOut.toByteArray()))) { + deserializedInstance = (SingletonInstance) in.readObject(); + } + + Assertions.assertSame(instance, deserializedInstance, "Singleton instance should be the same after deserialization"); } + @EnabledIf(value = "isRandom3") + @Test + public void testThreadSafe() { + assertThreadSafe(() -> SingletonEnum.INSTANCE); + assertThreadSafe(SingletonEager::getInstance); + assertThreadSafe(SingletonHolder::getInstance); + assertThreadSafe(SingletonAtomic::getInstance); + assertThreadSafe(SingletonSerializable::getInstance); + } + + private void assertThreadSafe(Supplier supplier) { + var first = CompletableFuture.supplyAsync(() -> { + var singleton = supplier.get(); + System.out.println("First ... " + singleton.getClass().getSimpleName()); + return singleton; + }); + + var second = CompletableFuture.supplyAsync(() -> { + var singleton = supplier.get(); + System.out.println("Second ... " + singleton.getClass().getSimpleName()); + return singleton; + }); + + SingletonInstance firstResult = first.join(); + SingletonInstance secondResult = second.join(); + + Assertions.assertSame(firstResult, secondResult); + Assertions.assertEquals(firstResult.getCreationTime(), secondResult.getCreationTime()); + } } \ No newline at end of file