diff --git a/java/app/src/main/java/design/pattern/singleton/DoubleCheckedLocking.java b/java/app/src/main/java/design/pattern/singleton/DoubleCheckedLocking.java new file mode 100644 index 0000000..c8925da --- /dev/null +++ b/java/app/src/main/java/design/pattern/singleton/DoubleCheckedLocking.java @@ -0,0 +1,26 @@ +package design.pattern.singleton; + +import java.util.concurrent.atomic.AtomicLong; + +public class DoubleCheckedLocking { + + private AtomicLong id = new AtomicLong(0); + private static DoubleCheckedLocking instance; + + private DoubleCheckedLocking() { + } + + public static DoubleCheckedLocking getInstance() { + if (instance == null) { + synchronized (DoubleCheckedLocking.class) { + instance = new DoubleCheckedLocking(); + } + } + + return instance; + } + + public long getId() { + return id.incrementAndGet(); + } +} diff --git a/java/app/src/main/java/design/pattern/singleton/EagerLoading.java b/java/app/src/main/java/design/pattern/singleton/EagerLoading.java new file mode 100644 index 0000000..6e186cd --- /dev/null +++ b/java/app/src/main/java/design/pattern/singleton/EagerLoading.java @@ -0,0 +1,20 @@ +package design.pattern.singleton; + +import java.util.concurrent.atomic.AtomicLong; + +public class EagerLoading { + + private AtomicLong id = new AtomicLong(0); + private static final EagerLoading instance = new EagerLoading(); + + private EagerLoading() { + } + + public static EagerLoading getInstance() { + return instance; + } + + public long getId() { + return id.incrementAndGet(); + } +} diff --git a/java/app/src/main/java/design/pattern/singleton/HolderIdiom.java b/java/app/src/main/java/design/pattern/singleton/HolderIdiom.java new file mode 100644 index 0000000..d3b08b2 --- /dev/null +++ b/java/app/src/main/java/design/pattern/singleton/HolderIdiom.java @@ -0,0 +1,24 @@ +package design.pattern.singleton; + +import java.util.concurrent.atomic.AtomicLong; + +public class HolderIdiom { + + private AtomicLong id = new AtomicLong(0); + + private HolderIdiom() { + } + + private static class SingletonHolderIdiom { + + private static final HolderIdiom instance = new HolderIdiom(); + } + + public static HolderIdiom getInstance() { + return SingletonHolderIdiom.instance; + } + + public long getId() { + return id.incrementAndGet(); + } +} diff --git a/java/app/src/main/java/design/pattern/singleton/LazyLoading.java b/java/app/src/main/java/design/pattern/singleton/LazyLoading.java new file mode 100644 index 0000000..9502161 --- /dev/null +++ b/java/app/src/main/java/design/pattern/singleton/LazyLoading.java @@ -0,0 +1,23 @@ +package design.pattern.singleton; + +import java.util.concurrent.atomic.AtomicLong; + +public class LazyLoading { + + private AtomicLong id = new AtomicLong(0); + private static LazyLoading instance; + + private LazyLoading() { + } + + public static synchronized LazyLoading getInstance() { + if (instance == null) { + instance = new LazyLoading(); + } + return instance; + } + + public long getId() { + return id.incrementAndGet(); + } +} diff --git a/java/app/src/test/java/design/pattern/singleton/DoubleCheckedLockingTest.java b/java/app/src/test/java/design/pattern/singleton/DoubleCheckedLockingTest.java new file mode 100644 index 0000000..67de57f --- /dev/null +++ b/java/app/src/test/java/design/pattern/singleton/DoubleCheckedLockingTest.java @@ -0,0 +1,53 @@ +package design.pattern.singleton; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class DoubleCheckedLockingTest { + + @Test + void sameInstance() { + DoubleCheckedLocking instance1 = DoubleCheckedLocking.getInstance(); + DoubleCheckedLocking instance2 = DoubleCheckedLocking.getInstance(); + + assertThat(instance1).isSameAs(instance2); + } + + @Test + void incrementId() { + DoubleCheckedLocking instance = DoubleCheckedLocking.getInstance(); + long id1 = instance.getId(); + long id2 = instance.getId(); + + assertThat(id1).isEqualTo(1L); + assertThat(id2).isEqualTo(2L); + } + + @Test + void getId_threadSafe() throws InterruptedException { + int numberOfThreads = 1000; + ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); + + Set generatedIds = new HashSet<>(); + + for (int i = 0; i < numberOfThreads; i++) { + executor.execute(() -> { + long id = DoubleCheckedLocking.getInstance().getId(); + synchronized (generatedIds) { + generatedIds.add(id); + } + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + + assertThat(generatedIds.size()).isEqualTo(numberOfThreads); + } +} diff --git a/java/app/src/test/java/design/pattern/singleton/EagerLoadingTest.java b/java/app/src/test/java/design/pattern/singleton/EagerLoadingTest.java new file mode 100644 index 0000000..1f16aca --- /dev/null +++ b/java/app/src/test/java/design/pattern/singleton/EagerLoadingTest.java @@ -0,0 +1,30 @@ +package design.pattern.singleton; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EagerLoadingTest { + + @Test + @DisplayName("두 객체가 동일 여부 확인") + void sameInstance() { + EagerLoading instance1 = EagerLoading.getInstance(); + EagerLoading instance2 = EagerLoading.getInstance(); + + assertThat(instance1).isSameAs(instance2); + } + + @Test + @DisplayName("객체가 생성 될 때마다 id가 증가") + void incrementAndGet() { + EagerLoading instance = EagerLoading.getInstance(); + + long id1 = instance.getId(); + long id2 = instance.getId(); + + assertThat(id1).isEqualTo(1); + assertThat(id2).isEqualTo(2); + } +} diff --git a/java/app/src/test/java/design/pattern/singleton/HolderIdiomTest.java b/java/app/src/test/java/design/pattern/singleton/HolderIdiomTest.java new file mode 100644 index 0000000..12d7680 --- /dev/null +++ b/java/app/src/test/java/design/pattern/singleton/HolderIdiomTest.java @@ -0,0 +1,26 @@ +package design.pattern.singleton; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class HolderIdiomTest { + + @Test + void sameInstance() { + HolderIdiom instance1 = HolderIdiom.getInstance(); + HolderIdiom instance2 = HolderIdiom.getInstance(); + + assertThat(instance1).isSameAs(instance2); + } + + @Test + void incrementAndGet() { + HolderIdiom instance = HolderIdiom.getInstance(); + long id1 = instance.getId(); + long id2 = instance.getId(); + + assertThat(id1).isEqualTo(1); + assertThat(id2).isEqualTo(2); + } +} diff --git a/java/app/src/test/java/design/pattern/singleton/LazyLoadingTest.java b/java/app/src/test/java/design/pattern/singleton/LazyLoadingTest.java new file mode 100644 index 0000000..8dbba85 --- /dev/null +++ b/java/app/src/test/java/design/pattern/singleton/LazyLoadingTest.java @@ -0,0 +1,54 @@ +package design.pattern.singleton; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class LazyLoadingTest { + + @Test + void sameInstance() { + LazyLoading instance1 = LazyLoading.getInstance(); + LazyLoading instance2 = LazyLoading.getInstance(); + + assertThat(instance1).isSameAs(instance2); + } + + @Test + void incrementId() { + LazyLoading instance = LazyLoading.getInstance(); + long id1 = instance.getId(); + long id2 = instance.getId(); + + assertThat(id1).isEqualTo(1); + assertThat(id2).isEqualTo(2); + } + + @Test + void incrementId_threadSage() throws InterruptedException { + int THREAD_SIZE = 1000; + ExecutorService executor = Executors.newFixedThreadPool(THREAD_SIZE); + Set generatedIds = new HashSet<>(); + + LazyLoading instance = LazyLoading.getInstance(); + + for (int i = 0; i < THREAD_SIZE; i++) { + executor.execute(() -> { + long id = instance.getId(); + synchronized (generatedIds) { + generatedIds.add(id); + } + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + + assertThat(generatedIds.size()).isEqualTo(THREAD_SIZE); + } +}