싱글톤 패턴
싱글톤 패턴은 특정 클래스의 인스턴스가 오직 하나임을 보장하고, 이 인스턴스에 접근할 수 있는 방법을 제공한다.
즉, 특정 클래스의 객체는 하나만 생성되도록 하여 동일 인스턴스를 재사용하는 패턴이다.
Singleton
- 생성자의 접근자를 private로 설정하여 새로운 인스턴스를 생성하지 못하도록 함
- 인스턴스의 접근을 위한 오퍼레이션을 static으로 정의하여 해당 메서드에 의해서만 인스턴스에 접근
- 인스턴스는 싱글턴 객체의 클래스 변수로 설정되어 클래스가 메모리에 데이터영역에 저장될 때 같이 저장됨
- 오직 한 번만 인스턴스를 생성할 수 있으며, 싱글톤 객체는 유일한 인스턴스의 생성에 대한 책임이 있음
이른 초기화 방식(Eager Initialization)
이른 초기화 방식은 싱글톤 패턴의 가장 기본적인 유형이다.
전역변수로 인스턴스를 생성하고 접근자를 private로 설정함으로써
싱글톤 객체의 오퍼레이션 메서드에 의해서만 접근이 가능하도록 만든다.
또한 생성자의 접근자를 private로 결정함으로써 외부에서 인스턴스를 생성할 수 없도록 만든다.
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
자바에서는 static으로 선언한 클래스 변수가 곧 전역변수이다. 클래스가 메모리의 데이터 영역에 저장될 때 클래스 변수인
instance는 싱글톤 인스턴스를 한 번만 생성하여 참조하기 때문에 인스턴스를 재사용하는 싱글톤 원칙을 지킨다.
장점
전역변수로 싱글톤 인스턴스를 만들었기 때문에 클래스 로더에 의하여 클래스가 로딩될 때 오직 한 번만 인스턴스가 생성됨
따라서 Thread-safe 하게 싱글톤 객체를 생성할 수 있음
단점
객체의 사용여부와 상관없이 클래스 로딩 시점에 인스턴스가 생성되어 프로그램이 종료될 때까지 메모리를 점유한다.
따라서 자주 사용하지 않는 객체라면 비효율적이게 된다.
늦은 초기화 방식(Lazy Initialization)
늦은 초기화 방식은 이른 초기화 방식과 반대로 클래스의 오퍼레이션 메서드가 호출될 때 인스턴스를 생성한다.
클래스 메서드가 호출될 때까지 메모리를 점유하지 않는다.
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
장점
필요할 때 객체를 생성할 수 있다.
단점
멀티 쓰레드 환경에서 동시에 호출되어 싱글톤 원칙을 보장할 수 없다.
쓰레드 안전 이른 초기화 방식(Thread safe Lazy Initialization)
늦은 초기화 방식을 보완하여 synchronized 키워드를 메서드에 선언하여 쓰레드 안전 상태로 싱글톤을 생성할 수 있다.
synchronized로 선언된 메서드는 하나의 쓰레드가 메서드를 사용하면 lock을 걸어 다른 쓰레드를 대기 상태로 만듦
public class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton instance = null;
private ThreadSafeLazySingleton() {}
public static synchronized ThreadSafeLazySingleton getInstance() {
if(instance == null){
instance = new ThreadSafeLazySingleton();
}
return instance;
}
}
장점
쓰레드 안전 상태로 싱글톤을 생성할 수 있다.
단점
synchronized가 선언된 메서드가 호출이 많으면 성능이 나빠질 수 있다.
DCL방식(Double-Checked locking)
DCL방식은 synchronized를 메서드에 선언하는 것이 아니라 메서드 내부에 synchronized블락을 만드는 방식이다.
public class DCLSingleton {
private volatile static DCLSingleton instance = null;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if(instance == null){
synchronized(DCLSingleton.class){
if(instance == null){
instance = new DCLSingleton();
}
}
}
return instance;
}
}
위 코드는 인스턴스가 없을 때 synchronized키워드를 통해 블락을 만들어 하나의 쓰레드만 작업할 수 있도록 한다.
또한 블락 내부에서 다시 한번 인스턴스 존재 여부를 체크하게 된다. 2번 체크하기 때문에 DCL방식이라 한다.
private접근자로 설정한 instance클래스를 보면 volatile라는 키워드를 사용했다.
하나의 프로세서는 최적화를 위해 변수를 메인 메모리에 바로 저장하지 않고 캐시메모리에 저장하여 성능을 향상시킨다.
하지만 volatile로 선언된 변수는 변수를 캐시메모리에 저장하지 않고 곧바로 메인 메모리에 반영하도록 한다.
volatile키워드를 사용하지 않고 멀티코어 환경에서 작업한다면 인스턴스를 캐시 메모리에 저장하여 메인 메모리에 인스턴스를 생성하기 전에 다른 코어의 쓰레드가 비어있는 메모리를 참조하기 때문에 문제가 발생할 수 있다.
Holder에 의한 초기화 방식(Initialization on demand holder idiom)
클래스 안에 클래스 홀더를 두어 JVM 클래스 로더 메커니즘과 클래스가 로드되는 시점을 이용한 방식이다.
현재 가장 많이 이용하는 방식이다.
public class StaticHolderSingleton {
private StaticHolderSingleton(){}
private static class SingleTonHolder {
private static final StaticHolderSingleton instance = new StaticHolderSingleton();
}
public static StaticHolderSingleton getInstance(){
return SingleTonHolder.instance;
}
}
클래스 안에 private 접근자로 중첩클래스를 선언하고 중첩 클래스 내부에 private접근자로 클래스 변수를 선언하였다.
중첩 클래스는 오퍼레이션 메서드에 의하여 호출되기 전까지 참조되지 않는다.
오퍼레이션 메서드에 의하여 호출될 때 클래스 로더에 의하여 싱글톤 인스턴스를 생성하게 된다.
중첩 클래스 내부에 선언된 클래스 변수를 static으로 선언되었기 때문에 중첩 클래스가 로드되는 시점에 한 번만 싱글톤 인스턴스를 생성할 수 있게 된다. 또한 final 키워를 통해 다시 값이 할당되지 않도록 하여 싱글톤 원칙을 지킨다.
'CS' 카테고리의 다른 글
전략 패턴(Strategy Pattern) (0) | 2023.05.10 |
---|---|
팩토리 메소드 패턴(Factory Method Pattern) (0) | 2023.04.29 |
컴퓨터 시스템의 구성 (0) | 2023.04.16 |
데이터베이스 언어 SQL (0) | 2023.04.15 |
관계 데이터 연산 (0) | 2023.04.15 |