From edc78ff2942af3108089ccf9f1dca2cb6b2013d1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-E1B1NK8\\user" Date: Wed, 2 Mar 2022 17:04:54 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EC=B5=9C=EC=9E=AC=EC=9A=B0=20-=20=EC=8B=B1?= =?UTF-8?q?=EA=B8=80=ED=86=A4=20=ED=8C=A8=ED=84=B4=20=EC=98=88=EC=A0=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/example-code" | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 "\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" new file mode 100644 index 0000000..06a3aab --- /dev/null +++ "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" @@ -0,0 +1,281 @@ +# 싱글톤 패턴 예제 + +예제 요구사항 + +- 사람인의 MUST 상품을 이용하는 기업을 관리(추가, 삭제, 적용 여부) +- MUST 구매 기업 리스트를 볼 수 있음 + +> **이른 초기화(Eager Initialization)** +> + +```java +/** + * MUST 관리 클래스 + */ +public class MustManage { + + //MUST 객체를 static을 사용해 생성함으로써 클래스 로더 시점에 메모리 등록 + private static final MustManage INSTANCE = new MustManage(); + + //MUST 구매 기업을 저장하기 위한 Set 저장소 + private final Set company = new HashSet<>(); + + //사용자가 마음대로 객체를 생성하지 못하게 막기 위해 private 선언 + private MustManage() {} + + //싱글톤 객체 얻기 + public static MustManage getInstance() { + return INSTANCE; + } + + /** + * 기업 MUST 적용 + */ + public void addCompany(String companyNm) { + //이미 구매한 기업인지 체크 + if(this.company.contains(companyNm)) { + System.out.println(companyNm + " 기업은 이미 MUST 적용중입니다."); + return; + } + + //기업 추가 + this.company.add(companyNm); + } + + /** + * 기업 MUST 제거 + */ + public void removeCompany(String companyNm) { + //기업이 존재하지 않으면 + if(!this.company.contains(companyNm)) { + System.out.println(companyNm + " 기업은 MUST가 적용되지 않았습니다."); + return; + } + + //제거 + this.company.remove(companyNm); + } + + /** + * MUST 적용중인 기업 출력 + */ + public void print() { + System.out.println("-----------MUST 적용 기업----------"); + //모든 기업 출력 + for (String s : this.company) { + System.out.println(s); + } + System.out.println("---------------------------------"); + } +} + +/** + * Main 실행 + */ +public class Main { + public static void main(String[] args) { + //싱글톤으로 이미 생성된 객체 가져옴 + MustManage mustManage = MustManage.getInstance(); + //기업 추가 + mustManage.addCompany("사람인"); + //기업 추가 + mustManage.addCompany("점핏"); + //출력 + mustManage.print(); + //기업 제거 + mustManage.removeCompany("점핏"); + //출력 + mustManage.print(); + //이미 적용중인 기업 추가 + mustManage.addCompany("사람인"); + //적용중이지 않은 기업 제거 + mustManage.removeCompany("점핏"); + + //다른 객체 + MustManage mustManage2 = MustManage.getInstance(); + //출력 + mustManage2.print(); + } +} + +//결과 +-----------MUST 적용 기업---------- +사람인 +점핏 +--------------------------------- +-----------MUST 적용 기업---------- +사람인 +--------------------------------- +사람인 기업은 이미 MUST 적용중입니다. +점핏 기업은 MUST가 적용되지 않았습니다. +-----------MUST 적용 기업---------- +사람인 +--------------------------------- +``` + +**이른 초기화(Eagar Initialization)**는 `static`을 이용해 컴파일 시점에 인스턴스를 메모리에 적재하는 `정적 바인딩(Static Binding)` 을 사용하는 방법입니다. + +- 장점 + - 컴파일 시점에 인스턴스를 미리 적재하기 때문에 Thread-Safe + - 미리 만들어두기 때문에 실제 인스턴스를 사용하지 않아도 메모리를 차지 + +> **늦은 초기화(Lazy Initialization)** +> + +```java +/** + * MUST 관리 클래스 + */ +public class MustManage { + + //MUST 객체 처음에는 NULL 상태 + private static MustManage INSTANCE; + + //싱글톤 객체 얻기 + public static MustManage getInstance() { + //INSTANCE가 NULL인 경우 객체 생성 + if (INSTANCE == null) { + INSTANCE = new MustManage(); + } + return INSTANCE; + } + + (...위 예제와 동일) +} +``` + +**늦은 초기화(Lazy Initialization)**는 실제 해당 객체가 사용될 때(`getInstance()` 호출) 생성하는 방법입니다. `동적 바인딩(Dynamic Binding)` + +하지만 위 코드는 아래처럼 동기화를 보장하지 않을 수 있습니다. + +```java +Thread A : if(INSTANCE == null) 수행 결과 true +Thread B : if(INSTANCE == null) 수행 결과 true + +Thread A : INSTANCE = new MustManage() 수행으로 인스턴스1 생성 +Thread B : INSTANCE = new MustManage() 수행으로 인스턴스2 생성 +``` + +- 장점 + - 이른 초기화 방법보다 메모리 측면에서 효율(사용할 때 생성 하기 때문) +- 단점 + - Thread-Safe X + +> **늦은 초기화, 동기화 처리(Lazy Initialization with synchronized)** +> + +```java +/** + * MUST 관리 클래스 + */ +public class MustManage { + + //MUST 객체 + private static MustManage INSTANCE; + + //synchronized 키워드를 사용 + public static synchronized MustManage getInstance() { + if(INSTANCE == null) { + INSTANCE = new MustManage(); + } + return INSTANCE; + } +} +``` + +`synchronized` 키워드를 사용하면 동기화를 가능하게하여 `Thread-Safe` 처리를 할 수 있습니다. +하지만 `getInstance()` 호출될 때 마다 동기화 작업이 이루어지기에 성능 하락이 발생할 수 있습니다. + +- 장점 + - 메모리 효율적 사용 + - thread-safe +- 단점 + - 인스턴스 생성 여부와 상관없는(`getInstacne()` 호출할 때마다) 동기화로 인한 성능 하락 + + +> **늦은 초기화, DCL(Lazy Initialization. Double Checked Locking)** +> + +```java +/** + * MUST 관리 클래스 + */ +public class MustManage { + + //volatile 키워드 사용 + private volatile static MustManage INSTANCE; + + //싱글톤 객체 얻기 + public static MustManage getInstance() { + //객체 존재여부를 먼저 체크 + if (INSTANCE == null) { + //synchronized를 이용한 동기화 객체 생성 + synchronized (MustManage.class) { + if (INSTANCE == null) { + INSTANCE = new MustManage(); + } + } + } + return INSTANCE; + } +} +``` + +이 방법은 위의 동기화 방식을 개선한 작업으로 객체 존재 여부를 먼저 체크해 무조건 `동기화 블록(synchronized)`로 넘어가지 않게 한 방법입니다. + +이 방법을 사용할 때는 객체 변수에 `volatile` 키워드를 사용해줘야합니다. + +`volatile` 키워드를 사용하면 CPU메모리 영역에 캐싱된 값이 아니라 항상 최신의 값을 가지도록 메인 메모리 영역에서 값을 참조합니다. + +그리고 컴파일 단계에서 `재배치(reordering)`를 방지해주는 기능이 있는데 쉽게 말하면 아래처럼 객체를 생성하는 코드가 있다고 했을 때 + +```java +INSTANCE = new MustManage(); +``` + +처리 되는 순서는 + +1. `MustManage` 인스턴스 생성 +2. `INSTANCE`에 인스턴스 주소 값 대입 + +이렇게 진행될텐데 컴파일러가 최적화에 따라 코드가 재배치 되서 아래처럼 순서가 반대로 될 수 있습니다. + +1. `INSTANCE`에 인스턴스 주소 값 대입 +2. `MustManage` 인스턴스 생성 + +이렇게 될 경우 여러개의 스레드가 들어왔을 때 **`INSTANCE`에 주소 값이 대입됐지만 인스턴스가 생성되지 않았을 때** 다른 스레드에서는 `null`이 아니기 때문에 정상적으로 다른 작업을 처리할 때 문제가 생길 수 있습니다. + +- 장점 + - 메모리 효율 + - Thread-Safe + - synchronized 키워드로 인한 성능 감소 해결 + + +> **LazyHolder - 늦은 초기화, Static Inner class사용** +> + +```java +/** + * MUST 관리 클래스 + */ +public class MustManage { + //내부 클래스를 사용하여 + private static final class MustManageHolder { + //내부클래스에 static 객체 생성 + private static final MustManage INSTANCE = new MustManage(); + } + + //싱글톤 객체 얻기 + public static MustManage getInstance() { + //내부클래스의 객체 가져오기 + return MustManageHolder.INSTANCE; + } +``` + +위 코드는 `Inner Static Class MustManageHolder` 를 선언하여 객체를 가져오는 방법이다. + +`Inner Static Class`는 해당 클래스를 사용할 때 초기화가 진행된다. 즉 `getInstance()` 가 호출 될 때 인스턴스가 메모리에 적재되기 때문에 늦은 초기화가 가능하다. 또한 클래스가 초기화하는 시점에는 `Thread-Safe`를 보장한다. + +다시 말해 `INSTANCE = new MustManage()` 구문은 클래스 초기화 시점에 호출되기에 한번만 호출된다. + +이 방식은 구현도 쉽고 `메모리 효율`, `Thread-Safe`를 모두 만족하기에 싱글톤 패턴을 적용할 때 가장 많이 사용하는 방식이다. \ No newline at end of file From 8fd8f5ec1bf48234adc6544a8f228b587a74c77b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Fri, 4 Mar 2022 08:53:18 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[Add]=20=EC=8B=B1=EA=B8=80=ED=86=A4=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\354\213\261\352\270\200\355\206\244.md" | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 "\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" new file mode 100644 index 0000000..1b92fc0 --- /dev/null +++ "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" @@ -0,0 +1,55 @@ +# + +# 싱글톤 + +> 인스턴스를 오직 한 개만 만들어서 제공하는 클래스가 필요한 경우에 사용하는 패턴 + +![https://www.notion.so/Users/LeeChnagSup/Library/Application%20Support/marktext/images/2022-03-01-17-45-58-image.png](https://user-images.githubusercontent.com/42997924/143546595-d548b627-e85d-4fed-be93-19ff66eafaa6.png) + +즉, 클래스가 최초 한번만 메로리를 할당받고 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴으로 생성자가 여러 차례 호출되더라도 실제 생성되는 인스턴스는 기존에 생성된 인스턴스이다. + +new 를 통해서 생성자를 만들어지면 같지 않은 경우가 생길수 있기 때문에 생성자를 private으로 제한하고 static 메서드를 통해서 생성하도록 해야한다. + +## 싱글톤은 언제 사용되는가? + +1. **메모리, 속도를 개선해야하는 경우** : 객체의 인스턴스를 재사용하기 때문(고정된 메모리 영역을 사용) +2. **데이터 공유가 필요한 경우** : 기존 인스턴스가 전역으로 사용되기 때문 +3. 인스턴스가 **한 개만 존재하는 것을 보장**하고 싶은 경우 + +## 예제 코드 + +//언제 사용되는지를 가지고 예제 코드를 설명 해줘야 함. + +## 패턴의 장/단점 + +장점: + +- 단 한개의 인스턴스를 만드는 클래스를 만들수 있다 +- 인스턴스의 글로벌 접근이 가능해진다. +- 싱글톤 객체는 처음 요청이 들어왔을때 딱 한번만 생성된다. + +단점: + +- SRP(단일 책임원칙)을 위배할 수도 있다. + +- 싱글톤 패턴은 나쁜 디자인 패턴이 될수도 있다. 프로그램의 컴포넌트들은 너무 많이 접근할 수 있게되면 안좋을 수도 있다. + +- 패턴은 멀티쓰레드 환경에서는 특별한 처리를 요구한다. + +- 싱글톤의 클라이언트 코드 유닛테스트가 어려움. 왜냐하면 많은 테스트 프레임워크들이 목객체를 생산할때 상속에 의존적이게 된다. + + 싱글톤 클래스의 생성자가 private, static method를 오버러이딩한 경우에는 대부분의 언어에서는 불가능함. 그래서 싱글톤 목객체 만드는 방식에 대해서 좀 다시 생각해봐야한다. 혹은 테스트 코드를 작성하지 말던가, 혹은 아예 싱글톤 패턴을 작성하지 않는 방식으로 처리할 수 있다. + +## 비슷한 패턴 + +- **파사드 패턴**: 파사드도 역시 하나의 객체로 충분히 가능한 경우가 많아서, 파사드 패턴 역시 싱글톤으로 변형 가능하다. + +- **플라이웨이트 패턴**: 플라이웨이트 패턴 역시 개체의 모든 공유 상태를 관리할 수 있다는 측면에서는 비슷할 수도 있다. + +       싱글톤 패턴과 약간 다른 점이 존재한다. + +       첫째, 싱글톤은 한개의 인스턴스만 가질 수 있지만, 플라이웨이트 패턴의 경우 서로 다른 고유 상태를 가진 인스턴스를 가질 수 있다. + +    둘째, 싱글톤 객체는 변형가능하지먼, 플라이웨이트는 변형 불가능하다. + +- **추상팩토리, 빌더, 프로토 타입**의 디자인 패턴 역시 싱글톤으로 구현 가능해진다. \ No newline at end of file From 7494c3bd77918b37307c183db5d173ce2c1ed6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Fri, 4 Mar 2022 09:28:58 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[Add]=20=EC=8B=B1=EA=B8=80=ED=86=A4=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\354\213\261\352\270\200\355\206\244.md" | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" index 1b92fc0..5acd223 100644 --- "a/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" +++ "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/\354\213\261\352\270\200\355\206\244.md" @@ -16,6 +16,46 @@ new 를 통해서 생성자를 만들어지면 같지 않은 경우가 생길수 2. **데이터 공유가 필요한 경우** : 기존 인스턴스가 전역으로 사용되기 때문 3. 인스턴스가 **한 개만 존재하는 것을 보장**하고 싶은 경우 +추가적으로 싱글톤의 경우 만드는 방식이 점진적으로 발전했었다. 아래 예제 코드에서도 설명하겠으나, 싱글톤을 만드는 방식은 총 6가지 정도 존재한다. + +- **이른 초기화 (Eager Initialization)** + + static을 통해 전역인스턴스를 만들고, get함수로 불러오는 방식 + + - 장점: Thread Safe함. + + - 단점: 미리 인스턴스를 만들어놓기 때문에 메모리적 손해 + +- **늦은 초기화 (Lazy Initialization)** + + static으로 선언하지만 미리 선언하지 않고, get함수로 불러올때 생성하는 방식 + + - 장점: 사용 시점에 인스턴스 생성 메모리 최적화 + + - 단점: Thread Safe하지 않음. + +- **늦은 초기화 + 동기화 (Lazy Initialization with synchronized)** + + 위의 방식에 sychronized 키워드와 같은 동기화 블록을 활용해 동기화 처리 + + - 장점: 메모리 효율적인 사용, Thread Safe + + - 단점: 인스턴스와 상관없이 Lock걸려서 성능 저하 + +- **늦은 초기화 + 더블체킹 락 (Lazy Initialization. Double Checked Locking)** + + - 장점: 메모리의 효율적 사용, Thread Safe, 인스턴스 생성 여부 검사(Lock 이슈 해결) + + - 단점: 비동기화된 Resource 필드에 의존적 -> 변수의 최신값, 원자성 보장 + +- **늦은 초기화 + Static Inner Class** + + Static inner Class를 만들어서 thread-safe하며, 호출될때 인스턴스가 만들어짐. + +- **늦은 초기화 + Enum** + + Enum class를 활용하면, 자동으로 singleton 인스턴스가 구성된다. + ## 예제 코드 //언제 사용되는지를 가지고 예제 코드를 설명 해줘야 함. From 97794e9496344cdac613cc64af1cf76bbfead101 Mon Sep 17 00:00:00 2001 From: YoungJun Park Date: Tue, 8 Mar 2022 23:06:27 +0900 Subject: [PATCH 4/4] Refactor File name (-md) --- .../summary/example-code.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename "\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" => "\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code.md" (99%) diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code.md" similarity index 99% rename from "\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" rename to "\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code.md" index 06a3aab..a569607 100644 --- "a/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code" +++ "b/\352\260\235\354\262\264\354\203\235\354\204\261/1\354\243\274\354\260\250-\354\213\261\352\270\200\355\206\244/summary/example-code.md" @@ -278,4 +278,4 @@ public class MustManage { 다시 말해 `INSTANCE = new MustManage()` 구문은 클래스 초기화 시점에 호출되기에 한번만 호출된다. -이 방식은 구현도 쉽고 `메모리 효율`, `Thread-Safe`를 모두 만족하기에 싱글톤 패턴을 적용할 때 가장 많이 사용하는 방식이다. \ No newline at end of file +이 방식은 구현도 쉽고 `메모리 효율`, `Thread-Safe`를 모두 만족하기에 싱글톤 패턴을 적용할 때 가장 많이 사용하는 방식이다.