From d1471d307fa6fb794a5404a4c04e055581b2abef Mon Sep 17 00:00:00 2001 From: jihwooon Date: Wed, 1 Jan 2025 17:34:14 +0900 Subject: [PATCH 1/4] =?UTF-8?q?EagerLoading=20=EA=B0=9D=EC=B2=B4=EB=A5=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EagerLoading 클래스를 구현하여 Singleton을 사용해 객체 유일성을 보장합니다. 주요 변경사항: - 해당 인스턴스 클래스 사용되는 시점이 아니라 미리 생성됩니다. - 클래스가 메모리에 적재될 때 미리 생성되어 초기화가 완료되어 스레드 안정성을 보장합니다. --- .../pattern/singleton/EagerLoading.java | 20 +++++++++++++ .../pattern/singleton/EagerLoadingTest.java | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 java/app/src/main/java/design/pattern/singleton/EagerLoading.java create mode 100644 java/app/src/test/java/design/pattern/singleton/EagerLoadingTest.java 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/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); + } +} From f12b28113619ad6948c04fc9392f23a5386ff5a3 Mon Sep 17 00:00:00 2001 From: jihwooon Date: Wed, 1 Jan 2025 17:59:53 +0900 Subject: [PATCH 2/4] =?UTF-8?q?LazyLoading=20=EA=B0=9D=EC=B2=B4=EB=A5=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LazyLoading 클래스를 생성하여 Singleton을 보장합니다. 주요 변경사항: - LazyLoading은 EagerLoading 와 비교해서 인스턴스 초기화가 발생하기 전에는 객체가 생성하지 않습니다. - 인스턴스가 많은 리소스를 점유하거나 초기화가 오래 걸리는 경우에는 인스턴스를 미리 만들어 초기화하는 것은 리소스 낭비입니다. - 하지만 인스턴스가 많은 리소스를 차지하고 있다면 fail-fast 설계 원칙에 따라 프로그램 시작 전 인스턴스를 초기화가 완료되는 것이 합리적입니다. --- .../design/pattern/singleton/LazyLoading.java | 23 ++++++++ .../pattern/singleton/LazyLoadingTest.java | 54 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 java/app/src/main/java/design/pattern/singleton/LazyLoading.java create mode 100644 java/app/src/test/java/design/pattern/singleton/LazyLoadingTest.java 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/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); + } +} From 39f279d0fe4c9fdf8bfc29c079f3a6a9d00214c4 Mon Sep 17 00:00:00 2001 From: jihwooon Date: Wed, 1 Jan 2025 18:26:08 +0900 Subject: [PATCH 3/4] =?UTF-8?q?DoubleCheckLocking=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DoubleCheckLocking 클래스를 생성하여 Singleton을 보장했습니다. 주요 변경사항: - 초기화 방식과 높은 동시성을 모두 지원하는 싱글턴 패턴 이중 잠금패턴을 적용했습니다. - 기존 즉시 초기화에 문제점을 해결하면서 동시성을 보장하는 패턴으로 두 가지 기능을 보장합니다. - CPU 명령이 재정렬되면 클래스의 객체가 instance 멤버 변수가 지정된 후, 초기화가 이루어지기 전에 다른 스레드에서 이 객체를 사용하려고 할 수 있다. - volatile 키워드를 인스턴스 멤버 변수에 추가로 명령어 재정렬을 방지하면 된다. --- .../singleton/DoubleCheckedLocking.java | 26 +++++++++ .../singleton/DoubleCheckedLockingTest.java | 53 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 java/app/src/main/java/design/pattern/singleton/DoubleCheckedLocking.java create mode 100644 java/app/src/test/java/design/pattern/singleton/DoubleCheckedLockingTest.java 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/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); + } +} From fc638fb74cf82041b9daf2c7b39325b34ccbbb13 Mon Sep 17 00:00:00 2001 From: jihwooon Date: Wed, 1 Jan 2025 19:03:36 +0900 Subject: [PATCH 4/4] =?UTF-8?q?HolderIdiom=20=EA=B0=9D=EC=B2=B4=EB=A5=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HolderIdiom 클래스를 생성하여 Singleton을 보장하였습니다. 주요 변경사항: - 이중 잠금보다 간단한 방식으로 Java 내부 클래스를 사용하는 방식입니다. - 인스턴스 유일성과 생성 프로세스의 스레드 안정성으로 JVM 의해 보장합니다. --- .../design/pattern/singleton/HolderIdiom.java | 24 +++++++++++++++++ .../pattern/singleton/HolderIdiomTest.java | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 java/app/src/main/java/design/pattern/singleton/HolderIdiom.java create mode 100644 java/app/src/test/java/design/pattern/singleton/HolderIdiomTest.java 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/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); + } +}