From b0c004a5a52999cfaaa3e94ca1848e8ac6f25729 Mon Sep 17 00:00:00 2001
From: hjoon <guswns3371@gmail.com>
Date: Sat, 26 Nov 2022 23:55:26 +0900
Subject: [PATCH] =?UTF-8?q?5=EC=A3=BC=EC=B0=A8:=20=EC=8B=B1=EA=B8=80?=
 =?UTF-8?q?=ED=86=A4=20=ED=8C=A8=ED=84=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/singleton/SingletonPattern.java           |   7 +
 src/singleton/boiler/ChocolateBoilerEnum.java |  48 ++++++
 .../boiler/ChocolateBoilerFinal.java          |  56 +++++++
 src/singleton/boiler/ChocolateBoilerLazy.java |  59 +++++++
 .../boiler/ChocolateBoilerSynchronized.java   |  61 +++++++
 .../boiler/ChocolateBoilerVolatile.java       |  65 ++++++++
 ...0\355\206\244 \355\214\250\355\204\264.md" | 151 ++++++++++++++++++
 7 files changed, 447 insertions(+)
 create mode 100644 src/singleton/SingletonPattern.java
 create mode 100644 src/singleton/boiler/ChocolateBoilerEnum.java
 create mode 100644 src/singleton/boiler/ChocolateBoilerFinal.java
 create mode 100644 src/singleton/boiler/ChocolateBoilerLazy.java
 create mode 100644 src/singleton/boiler/ChocolateBoilerSynchronized.java
 create mode 100644 src/singleton/boiler/ChocolateBoilerVolatile.java
 create mode 100644 "src/singleton/\354\213\261\352\270\200\355\206\244 \355\214\250\355\204\264.md"

diff --git a/src/singleton/SingletonPattern.java b/src/singleton/SingletonPattern.java
new file mode 100644
index 0000000..bd50699
--- /dev/null
+++ b/src/singleton/SingletonPattern.java
@@ -0,0 +1,7 @@
+package singleton;
+
+public class SingletonPattern {
+    public static void main(String[] args) {
+
+    }
+}
diff --git a/src/singleton/boiler/ChocolateBoilerEnum.java b/src/singleton/boiler/ChocolateBoilerEnum.java
new file mode 100644
index 0000000..3adaa3f
--- /dev/null
+++ b/src/singleton/boiler/ChocolateBoilerEnum.java
@@ -0,0 +1,48 @@
+package singleton.boiler;
+
+/**
+ * enum으로 싱글톤을 생성하면
+ * 동기화 문제, 클래스 로딩 문제, 리플렉션, 직렬화, 역직렬화 문제를 해결할 수 있다.
+ */
+public enum ChocolateBoilerEnum {
+    UNIQUE_INSTANCE;
+
+    private boolean empty;
+    private boolean boiled;
+
+    /**
+     * 보일러가 비어있을 경우에만 재료를 넣는다
+     */
+    public void fill() {
+        if (isEmpty()) {
+            empty = false;
+            boiled = false;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 다 끓여진 상태에서만 초콜릿을 흘려보낸다
+     */
+    public void drain() {
+        if (!isEmpty() && isBoiled()) {
+            empty = true;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 아직 끓지않은 상태에서만 끓인다
+     */
+    public void boil() {
+        if (!isEmpty() && !isBoiled()) {
+            boiled = true;
+        }
+    }
+
+    public boolean isEmpty() {
+        return empty;
+    }
+
+    public boolean isBoiled() {
+        return boiled;
+    }
+}
diff --git a/src/singleton/boiler/ChocolateBoilerFinal.java b/src/singleton/boiler/ChocolateBoilerFinal.java
new file mode 100644
index 0000000..8dfb9de
--- /dev/null
+++ b/src/singleton/boiler/ChocolateBoilerFinal.java
@@ -0,0 +1,56 @@
+package singleton.boiler;
+
+/**
+ * public static final field
+ */
+public class ChocolateBoilerFinal {
+    private boolean empty;
+    private boolean boiled;
+
+    // 싱글톤 인스턴스를 처음부터 초기화한다.
+    private static final ChocolateBoilerFinal uniqueInstance = new ChocolateBoilerFinal();
+
+    private ChocolateBoilerFinal() {
+        empty = true;
+        boiled = false; // 보일러가 비어있을 때만 돌아간다
+    }
+
+    public static ChocolateBoilerFinal getInstance() {
+        return uniqueInstance;
+    }
+
+    /**
+     * 보일러가 비어있을 경우에만 재료를 넣는다
+     */
+    public void fill() {
+        if (isEmpty()) {
+            empty = false;
+            boiled = false;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 다 끓여진 상태에서만 초콜릿을 흘려보낸다
+     */
+    public void  drain() {
+        if (!isEmpty() && isBoiled()) {
+            empty = true;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 아직 끓지않은 상태에서만 끓인다
+     */
+    public void boil() {
+       if (!isEmpty() && !isBoiled()) {
+           boiled = true;
+       }
+    }
+    public boolean isEmpty()  {
+        return empty;
+    }
+
+    public boolean isBoiled() {
+        return boiled;
+    }
+}
diff --git a/src/singleton/boiler/ChocolateBoilerLazy.java b/src/singleton/boiler/ChocolateBoilerLazy.java
new file mode 100644
index 0000000..febb51d
--- /dev/null
+++ b/src/singleton/boiler/ChocolateBoilerLazy.java
@@ -0,0 +1,59 @@
+package singleton.boiler;
+
+/**
+ * lazy initialization -> 동기화 문제
+ */
+public class ChocolateBoilerLazy {
+    private boolean empty;
+    private boolean boiled;
+
+    private static ChocolateBoilerLazy uniqueInstance;
+
+    private ChocolateBoilerLazy() {
+        empty = true;
+        boiled = false; // 보일러가 비어있을 때만 돌아간다
+    }
+
+    public static ChocolateBoilerLazy getInstance() {
+        if (uniqueInstance == null) { // 멀티 스레딩 환경에서 최초로 인스턴스가 생성될 시점에 동기화 문제 발생
+            uniqueInstance = new ChocolateBoilerLazy();
+        }
+
+        return uniqueInstance;
+    }
+
+    /**
+     * 보일러가 비어있을 경우에만 재료를 넣는다
+     */
+    public void fill() {
+        if (isEmpty()) {
+            empty = false;
+            boiled = false;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 다 끓여진 상태에서만 초콜릿을 흘려보낸다
+     */
+    public void  drain() {
+        if (!isEmpty() && isBoiled()) {
+            empty = true;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 아직 끓지않은 상태에서만 끓인다
+     */
+    public void boil() {
+       if (!isEmpty() && !isBoiled()) {
+           boiled = true;
+       }
+    }
+    public boolean isEmpty()  {
+        return empty;
+    }
+
+    public boolean isBoiled() {
+        return boiled;
+    }
+}
diff --git a/src/singleton/boiler/ChocolateBoilerSynchronized.java b/src/singleton/boiler/ChocolateBoilerSynchronized.java
new file mode 100644
index 0000000..ac490fb
--- /dev/null
+++ b/src/singleton/boiler/ChocolateBoilerSynchronized.java
@@ -0,0 +1,61 @@
+package singleton.boiler;
+
+/**
+ * lazy initialization & synchronized
+ */
+public class ChocolateBoilerSynchronized {
+    private boolean empty;
+    private boolean boiled;
+
+    private static ChocolateBoilerSynchronized uniqueInstance;
+
+    private ChocolateBoilerSynchronized() {
+        empty = true;
+        boiled = false; // 보일러가 비어있을 때만 돌아간다
+    }
+
+    // 인스턴스가 최초로 초기화 되는 시점에만 동기화가 필요하지만,
+    // 이 방식은 항상 동기화가 일어나 속도 문제가 있다.
+    public static synchronized ChocolateBoilerSynchronized getInstance() {
+        if (uniqueInstance == null) {
+            uniqueInstance = new ChocolateBoilerSynchronized();
+        }
+
+        return uniqueInstance;
+    }
+
+    /**
+     * 보일러가 비어있을 경우에만 재료를 넣는다
+     */
+    public void fill() {
+        if (isEmpty()) {
+            empty = false;
+            boiled = false;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 다 끓여진 상태에서만 초콜릿을 흘려보낸다
+     */
+    public void  drain() {
+        if (!isEmpty() && isBoiled()) {
+            empty = true;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 아직 끓지않은 상태에서만 끓인다
+     */
+    public void boil() {
+       if (!isEmpty() && !isBoiled()) {
+           boiled = true;
+       }
+    }
+    public boolean isEmpty()  {
+        return empty;
+    }
+
+    public boolean isBoiled() {
+        return boiled;
+    }
+}
diff --git a/src/singleton/boiler/ChocolateBoilerVolatile.java b/src/singleton/boiler/ChocolateBoilerVolatile.java
new file mode 100644
index 0000000..5aabc00
--- /dev/null
+++ b/src/singleton/boiler/ChocolateBoilerVolatile.java
@@ -0,0 +1,65 @@
+package singleton.boiler;
+
+/**
+ * lazy initialization & volatile
+ */
+public class ChocolateBoilerVolatile {
+    private boolean empty;
+    private boolean boiled;
+
+    private volatile static ChocolateBoilerVolatile uniqueInstance;
+
+    private ChocolateBoilerVolatile() {
+        empty = true;
+        boiled = false; // 보일러가 비어있을 때만 돌아간다
+    }
+
+    public static ChocolateBoilerVolatile getInstance() {
+        if (uniqueInstance == null) {
+            // 인스턴스가 최초로 초기화되는 시점에만 동기화가 일어난다.
+            // 다만 java 5 버전 이하의 JVM에서는 volatile 키워드가 제대로 동작하지 않는 이슈가 있다.
+            synchronized (ChocolateBoilerVolatile.class) {
+                if (uniqueInstance == null) {
+                    uniqueInstance = new ChocolateBoilerVolatile();
+                }
+            }
+        }
+
+        return uniqueInstance;
+    }
+
+    /**
+     * 보일러가 비어있을 경우에만 재료를 넣는다
+     */
+    public void fill() {
+        if (isEmpty()) {
+            empty = false;
+            boiled = false;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 다 끓여진 상태에서만 초콜릿을 흘려보낸다
+     */
+    public void  drain() {
+        if (!isEmpty() && isBoiled()) {
+            empty = true;
+        }
+    }
+
+    /**
+     * 보일러가 가득차있고, 아직 끓지않은 상태에서만 끓인다
+     */
+    public void boil() {
+       if (!isEmpty() && !isBoiled()) {
+           boiled = true;
+       }
+    }
+    public boolean isEmpty()  {
+        return empty;
+    }
+
+    public boolean isBoiled() {
+        return boiled;
+    }
+}
diff --git "a/src/singleton/\354\213\261\352\270\200\355\206\244 \355\214\250\355\204\264.md" "b/src/singleton/\354\213\261\352\270\200\355\206\244 \355\214\250\355\204\264.md"
new file mode 100644
index 0000000..47cc1bb
--- /dev/null
+++ "b/src/singleton/\354\213\261\352\270\200\355\206\244 \355\214\250\355\204\264.md"	
@@ -0,0 +1,151 @@
+# 5주차: 싱글톤패턴
+
+# 싱글톤 패턴
+
+인스턴스가 2개 이상이면 자원을 불필요하게 차지하거나 결과에 일관성이 없어지는 문제가 생길 수 있다. 이러한 문제를 해결하기 위해 싱글톤 패턴을 활용한다.
+
+인스턴스가 유일무이해야 하는 경우
+
+- 레지스트리 설정이 담겨있는 객체
+- ConnectionPool, ThreadPool과 같은 자원을 관리하는 객체
+
+> 싱글톤 패턴
+>
+
+특정 클래스에 객체 인스턴스가 오직 하나만 만들어지도록 해주는 디자인 패턴
+
+- 다른 클래스에서 싱글톤 클래스의 인스턴스를 추가로 만들기 못하게 해야 한다
+- 싱글톤 클래스 속 인스턴스에 접근할 수 있도록 전역 접근 지점을 제공해야한다.
+
+# 고전적인 싱글톤 패턴
+
+lazy initialization
+
+```java
+public class Singleton {
+
+	private static Singleton uniqueInstance; // Singleton 클래스의 하나뿐인 인스턴스를 저장하는 정적 변수
+
+	private Singleton() {} // 생성자를 private으로 선언하였기 때문에, Singleton에서만 클래스 인스턴스를 만들 수 있다
+
+	public static Singleton getInstance() {
+		if (uniqueInstance == null) {
+				uniqueInstance = new Singleton(); // lazy instantiation (게으른 인스턴스 생성)
+		}
+		return uniqueInstance;
+	}
+}
+```
+
+- 생성자를 private으로 선언하여 Singleton 클래스 내부에서만 인스턴스를 생성할 수 있도록 제한한다.
+- public으로 선언된 생성자가 없고, 정적 메소드로 인스턴스를 생성한다.
+- 게으른 인스턴스 생성 방식을 통해 인스턴스가 필요한 상황이 닥치기 전까지 인스턴스를 생성하지 않는다.
+
+## 멀티 스레딩 환경의 문제
+
+서로 다른 두 스레드가 다른 싱글톤 객체를 사용할 수 있기 때문에 동기화 문제가 존재한다.
+
+> synchronized 키워드
+>
+- 이를 해결하기 위해 getInstance() 메소드에 (전역 접근 지점) `synchronized` 키워드를 추가하면 된다.
+- synchronized 키워드를 사용하면 한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드가 기다려야 하기 때문에 멀티 스레드 환경의 동기화 문제를 해결할 수 있다.
+
+> 속도 문제
+>
+
+동기화가 필요한 시점은 getInstance() 메소드가 최초로 처음 시작될 시점 뿐이다. → uniqueInstance 변수에 Singleton 인스턴스가 대입된 시점부터 synchronized 키워드의 의미가 없어진다.
+
+다시 말해 처음을 제외하면 동기화는 불필요한 오버헤드만 증가시켜 속도 문제를 일으킨다.
+
+# 멀티스레딩 환경에서의 싱글톤 패턴
+
+### 1. 동기화 속도 문제가 크게 중요하지 않다면 고전적인 방식을 활용한다.
+
+다만 메소드를 동기화하면 성능이 100배 정도 저하된다.
+
+### 2. 싱글톤 인스턴스가 필요할 때에 생성하지 않고 (게으른 인스턴스 생성), 처음부터 만든다
+
+public static final field
+
+```java
+public class Singleton {
+
+	// 정적 초기화 부분에서 싱글톤 인스턴스를 생성한다.
+	private static final Singleton uniqueInstance = new Singleton();
+
+	private Singleton() {}
+
+	public static Singleton getInstance() {
+			return uniqueInstance;
+	}
+}
+```
+
+### 3. DCL(Double-Check Locking)을 활용하여 getInstance() 메소드에서 동기화되는 부분을 줄인다
+
+uniqueInstance가 최초로 생성되는 시점에만 동기화하고 나중에는 동기화하지 않는 방식이다
+
+- java 1.4 이전 버전에는 volatile 키워드를 사용해도 DCL에서 동기화가 제대로 안되는 경우가 많다. 따라서 java 5버전 보다 낮은 JVM을 사용한다면 이 방식은 바람직하지 않다.
+
+```java
+public class Singleton {
+
+	private volatile static Singleton uniqueInstance; // volatile
+
+	private Singleton() {} 
+	public static Singleton getInstance() {
+
+		if (uniqueInstance == null) { // 인스턴스가 초기화 되었는지 확인
+				synchronized (Singleton.class) { // 최초로 인스턴스가 초기화 되는 시점에만 동기화 된다
+					if (uniqueInstance == null) {
+						uniqueInstance = new Singleton();
+				}
+			}
+		}
+		return uniqueInstance;
+	}
+}
+```
+
+# 싱글톤 패턴의 문제점
+
+> 클래스 로더 문제
+>
+
+클래스 로더마다 서로 다른 namespace를 정의한다. 따라서 클래스 로더가 2개 이상이라면 같은 클래스를 여러번 로딩할 수 있기 때문에 싱글톤 객체가 여러 개 만들어지는 문제가 있다.
+
+- 클래스 로더를 직접 지정하면 해당 문제를 피할 수 있다.
+
+> 리플렉션, 직렬화, 역직렬화 문제 ([참고](https://scshim.tistory.com/361))
+>
+
+리플렉션, 직렬화, 역직렬화를 사용할 경우 싱글톤 패턴이 문제가 될 수 있다.
+
+- 리플렉션을 이용하면 런타임에 private 생성자에 접근하여 또 다른 싱글톤 인스턴스를 생성할 수 있다
+- 클래스를 역직렬화하면 새로운 인스턴스가 생성되어 싱글톤 패턴의 속성을 위반하게 된다
+
+> 싱글톤 패턴은 느슨한 결합에 위배 된다
+>
+
+싱글톤 클래스에 의존하는 경우 단단하게 결합된다는 문제가 있다.
+
+# enum을 활용한 싱글톤 패턴
+
+enum으로 싱글톤을 생성하면 → 동기화 문제, 클래스 로딩 문제, 리플렉션, 직렬화, 역직렬화 문제를 해결할 수 있다.
+
+```java
+public enum Singleton {
+		UNIQUE_INSTANCE;
+		int value;
+		
+		public int getValue() { return value; }
+		public void setValue(int value) { this.value = value; }
+}
+
+public class SingletonClient {
+	public static void main(String[] args) {
+		Singleton singleton = Signleton.UNIQUE_INSTANCE;
+		singleton.setValue(2);
+	}
+}
+```
\ No newline at end of file
