싱글턴 패턴(Singleton Pattern) 이란?
싱글턴 패턴(Singleton Pattern) 이란 생성자를 여러 차례를 하나로 유지하는 것을 의미합니다. 객체가 최초로 생성된 이후에 생성자나 객체 생성 메서드는 기존에 만들어진 객체를 반환합니다.
→ 프로그램 전체에서 객체를 단 하나만 생성하고, 어디서든 그 객체에 접근할 수 있게 한다.
예를 들어 DB 연결 객체가 있다고 가정했을 때,
DBConnection db1 = new DBConnection();
DBConnection db2 = new DBConnection();
DBConnection db3 = new DBConnection();아래와 같은 문제점이 발생할 수 있다
- DB 연결 객체가 계속 생성됨
- 메모리 낭비
- 연결 충돌 가능 → (?)
그래서 DBConnection은 프로그램 전체에서 하나만 존재 하도록 만들고 싶을 때 사용하는 것이 싱글턴 패턴 이다.
싱글턴 패턴 구조
싱글턴의 3가지 규칙
- 생성자를 private로 막는다.
- static으로 자기 자신 객체를 저장한다.
- 객체를 가져오는 메서드를 제공한다.
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
// s1 == s2 -> true싱글턴 구현 방식
- 즉시 생성(Eager)
private static Singleton instance = new Singleton();- 필드에 자기자신 타입의 인스턴스를 필드로 하나 보유하고, 바로 초기화하는 방식
- 장점: Thread-safe, 생성 속도가 빠름
- 단점: 사용하지 않아도 생성되어 메모리 공간을 차지함. 생성된 인스턴스를 사용하지 않더라도 인스턴스가 항상 무조건 생성되어 예외 처리를 할 수 있는 방법이 없음.
- 지연 생성(Lazy)
if(instance == null)- 처음 필드를 생성할 때 초기화하지 않고 getInstance() 메소드를 호출하게 되면 초기화를 시도하는 방식
- 장점: 메모리 절약
- 단점: Thread-safe 하지 않음, 여러개의 쓰레드가 동시에 요청할 경우 싱글톤의 유일성을 보장할 수 없음 (다중 인스턴스가 생길 수 있음)
- synchronized 방식
public static synchronized Singleton getInstance()- Lazy 방식을 보완할 수 있는 방법, 쓰레드가 이 인스턴스를 생성하는 임계영역에 진입할 때 순차적으로 진입할 수 있도록 하는 방식
- 동시에 여러 쓰레드가 인스턴스 생성 메소드에 접근하지 못하도록 하나의 인스턴스만 생성됨을 보장함
- 장점: Thread-safe
- 단점: synchronized 키워드 사용으로 성능이 저하될 수 있음 (하나의 쓰레드만 초기화 임계영역에 접근 가능하기 때문)
- Double Checked Locking Pattern (DCLP)
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}- 첫번째로 인스턴스가 null인지 체크해 인스턴스가 이미 생성되었는지 아닌지를 체크하고, 생성된게 없는 경우에만 synchronized 키워드에 기반하여 Thread-safe하게 인스턴스를 생성하는 방식
- 장점: synchronized 방식보다 성능 향상
- 단점: 메소드 내부의 코드가 많아짐 (가독성 저하)
- volatile
싱글턴과 static의 차이점
static은 객체가 없는 클래스 수준 기능이고 다형성이나 인터페이스 구현이 어려움. 반면 싱글턴은 객체가 존재하기 때문에 객체지향 설계가 가능하고 의존성 주입이나 확장이 가능함.
싱글턴 패턴의 장단점
장점
- 최초 한번의 new 연산자를 통해 고정된 메모리 영역을 사용하기 때문에 객체에 접근할 때 메모리 낭비를 방지할 수 있고, 이미 생성된 인스턴스를 활용하기 때문에 속도가 빠르다.
- 클래스간 데이터 공유가 쉽다. 전역으로 사용되는 인스턴스기 때문에 다른 클래스 인스턴스들이 접근하여 사용할 수 있다. (다만 동시성 문제가 발생할 수 있음)
- 도메인 관점에서 인스턴스가 한개만 존재하는 것을 보증하고 싶을 때도 싱글턴 패턴 사용
단점
- 동시성 문제 해결을 위해 synchronized 키워드를 사용해야 해서 복잡성이 올라간다.
- 테스트하기 어렵다. 인스턴스는 자원을 공유하고 있기 때문에 테스트가 격리된 환경에서 수행되려면 매번 인스턴스의 상태를 초기화시켜주어야 한다.
- 의존 관계상 클라이언트가 구체 클래스에 의존하게 된다. → (?)
- 자식 클래스를 만들 수 없다.
- 내부 상태를 변경하기 어렵다.