일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 조합
- MSA
- DDL
- java
- KEVISS
- equals
- VUE
- DI
- 인덱스
- 열 속성
- AOP
- jpa
- 필드 주입
- 테스트 코드
- 재정의
- static
- docker
- Spring
- StringBuilder
- jwt
- hashcode
- SQL
- lambda
- redis
- select_type
- cache
- 바이너리 카운팅
- Test
- 생성자 주입
- stream
- Today
- Total
백엔드 개발자 블로그
싱글턴 패턴 구현 방법 본문
첫 번째로 소개할 디자인 패턴은 싱글톤(Singleton) 패턴입니다.
종종 싱글톤 패턴을 '단일체' 패턴으로 번역하고 있는 책도 있지만, 일반적으로 싱글톤 패턴이라고 부릅니다.
싱글톤 패턴은 객체지향 디자인 패턴에서 가장 유명한 패턴 중 하나로, 디자인 패턴을 따로 공부하지 않으신 분들도 익히 알고 있는 패턴입니다.
하지만 유명한 만큼 예제 코드를 쉽게 접할 수 있어서인지 프로젝트에 싱글톤을 어설프게 적용은 하지만 정작 왜 써야 하는지, 어떻게 써야 내 상황에 맞게 잘 쓰는지에 대해서는 잘 모르시는 분들이 많습니다.
이번 포스팅에서는 디자인 패턴 관점에서 싱글톤의 개념과 역할에 대해 살펴보고, Java를 통해 간단한 구현까지도 실습해보도록 하겠습니다.
싱글톤은 생성 패턴(Creational Pattern) 중 하나이다.
생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴입니다.
생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해줍니다.
생성 패턴은 시스템이 상속(inheritance) 보다 복합(composite) 방법을 사용하는 방향으로 진화되어 가면서 더 중요해지고 있습니다.
생성 패턴에서는 중요한 이슈가 두 가지 있습니다.
- 생성 패턴은 시스템이 어떤 Concrete Class를 사용하는지에 대한 정보를 캡슐화합니다.
- 생성 패턴은 이들 클래스의 인스턴스들이 어떻게 만들고 어떻게 결합하는지에 대한 부분을 완전히 가려줍니다.
쉬운 말로 정리 하자면, 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 확보할 수 있게 됩니다.
생성 패턴에 어떤 패턴들이 있는지 궁금하신 분들은 이전 글을 참고하시기 바랍니다.
싱글톤(Singleton) 패턴이란?
싱글톤 패턴은 어떤 클래스의 인스턴스가 오직 하나임을 보장하며, 이 인스턴스에 접근할 수 있는 전역적인 접촉점을 제공하는 패턴입니다.
정리하자면, 프로그램 시작부터 종료 시까지 어떤 클래스의 인스턴스가 메모리 상에 단 하나만 존재할 수 있게 하고 이 인스턴스에 대해 어디에서나 접근할 수 있도록 하는 패턴입니다.
싱글톤 패턴은 왜 고안되었을까요?
개발을 하다보면 어떤 클래스에 대해 단 하나의 인스턴스만을 갖도록 하는 것이 좋은 경우가 있습니다.
예를 들어, 로그를 찍는(Logging) 객체라던가 쓰레드 풀, 윈도우 관리자 등 여러 객체를 관리하는 역할의 객체는 프로그램 내에서 단 하나의 인스턴스를 갖는 것이 바람직합니다.
그러면 그 하나의 인스턴스를 어떻게 접근할 수 있을까요?
흔히 어디에서나 접근할 수 있다고 한다면 "전역 변수"를 떠올리기가 쉽습니다. 물론 "틀렸다"라고는 할 수 없겠으나 이보다 더 좋은 방법은 클래스 자신이 자기의 유일한 인스턴스로 접근하는 방법을 자체적으로 관리하는 것입니다.
쉽게 말해, 생성자를 private하게 만들어 클래스 외부에서는 인스턴스를 생성하지 못하게 차단하고, 내부에서 단 하나의 인스턴스를 생성하여 외부에는 그 인스턴스에 대한 접근 방법을 제공할 수 있습니다.
기본적인 싱글톤(Singleton) 구현 방법
싱글톤 패턴을 구현하는 방법은 굉장히 다양합니다.
그러나 각각의 패턴이 공통적으로 갖는 특징이 있는데, 이는 다음과 같습니다.
- private 생성자 : 외부 클래스로부터 인스턴스 생성을 차단합니다.
- private static 인스턴스 : 인스턴스를 하나만 생성합니다.
- public static 메소드 : 외부에서 접근할 수 있도록 접점을 제공합니다.
지금부터 싱글톤을 구현하는 6가지 방법에 대해 살펴보겠습니다.
1. Eager Initialization
- 특징
- 가장 간단한 형태의 구현 방법입니다.
- 클래스 로딩 단계에서 인스턴스가 생성됩니다.
- 단점
- 메모리 비효율적
- Exception Handling을 못합니다.
- Not Thread Safe
- 코드
public class Singleton {
private static final Singleton instance = new Singleton();
// private constructor to avoid client applications to use constructor
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
2. Static Block Initialization
- static block을 추가 -> Exception Handling문제 해결
- 단점
- 메모리 비효율적
- Not Thread Safe
- 코드
public class Singleton {
private static Singleton instance;
private Singleton(){};
//static block initialization for exception handling
static{
try{
instance = new Singleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static Singleton getInstance(){
return instance;
}
}
3. Lazy Initialization
- 나중에(인스턴스를 사용시) 초기화하는 방법입니다. -> 메모리 비효율성 문제 해결
- 단점
- Not Thread Safe
- 코드
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){ // 나중에 초기화
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
4. Thread Safe Singleton
- getInstance() 메소드에 synchronized를 걸어두는 방식입니다. -> Thread Safe
- 단점
- synchronized의 잦은 호출로 성능 저하
- 코드
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
5. Double Checked Locking
- instance가 null일 경우에만 synchronized가 동작하도록 하여 synchronized 호출 횟수를 줄였습니다.
- 단점
- 여전히 synchronized 호출
- 코드
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
6. Bill Pugh Singleton Implementaion
- 현재 가장 널리 쓰이는 싱글톤 구현 방법입니다.
- private inner static class를 통해 synchronized의 성능저하 문제를 해결했습니다.
- 코드
public class Singleton {
private Singleton(){}
private static class SingletonHelper{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
7. Enum Singleton
- Enum으로 Reflection문제를 해결한 방법입니다.
- 단점
- 메모리 비효율적
- 유연성이 떨어짐
- 코드
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
6번에서 살펴본 inner static class 방식을 사용하는 것이 대부분의 경우에 최선의 방법이 아닐까 생각됩니다.
reflection 문제가 심각하다면 7번 방법을 사용합니다.