Singleton Pattern (싱글톤 패턴)은 특정 클래스에 대해서 객체 인스턴스가 하나만 만들어질 수 있도록 해주는 패턴이다.
대신 전역 변수를 사용하게되면 애플리케이션이 시작될 때 생성되고 사용하지 않을 경우 자원만 잡아먹는 꼴이 된다. 하지만 싱글톤 패턴은 필요할 때만 객체를 만들 수 있다.
기본 구현
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
//인스턴스가 생성되지 않았다면 생성
if (uniqueInstance null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
멀티 스레딩의 동기화 문제
위 기본 구현
코드의 if (uniqueInstance null) {
라인에서 context switching이 일어나면 인스턴스가 여러 개 생성될 수 있다.
thread1 | thread2 |
---|---|
if (uniqueInstance null) { |
|
if (uniqueInstance null) { |
|
uniqueInstance = new Singleton(); |
|
uniqueInstance = new Singleton(); |
즉, thread1과 thread2 모두에서 인스턴스가 생성되지 않았다고 판단하여 객체를 새로 생성하게 되는 것이다.
이런 동기화 문제를 해결하기위해서 synchronized
키워드를 사용할 수 있다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
//한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드는 기다려야 한다.
public static synchronized Singleton getInstance() {
if (uniqueInstance null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
이로 인해 두 스레드가 메소드를 동시에 실행시킬 수 없게 된다.
동기화의 속도 문제
일단 uniqueInstance
변수에 Singleton
인스턴스를 대입하고 나면 이후에는 동기화된 상태로 유지할 필요가 없다. 다시말해 인스턴스를 생성하는 첫 번째 과정을 제외하면 동기화가 필요없다.
1. 애플리케이션에 큰 부담을 주지 않는다면 그냥 둔다.
2. 인스턴스를 처음에 만들어 버린다.
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static synchronized Singleton getInstance() {
return uniqueInstance;
}
}
이 방법은 인스턴스를 항상 사용할 경우 고려할 수 있는 방법이다. 사용하지 않을 경우 자원만 차지하게 된다.
3. DCL (Double-checking Locking)
DCL을 사용하면 인스턴스가 생성되어 있지 않았을 때만 동기화를 할 수 있다.
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
//인스턴스가 있는지 확인하고 없으면 동기화 블록으로 진입
if (uniqueInstance null) {
synchronized (Singleton.class) {
//동기화 블록으로 들어간 후에도 null 체크
if (uniqueInstance null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- 동기화 블록 안에서도 null 체크를 하는 이유는 다른 스레드에서 이미 인스턴스를 생성했을 수도 있기 때문이다.
volatile
- Main Memory에 저장하겠다는 것을 명시한다.
- 멀티스레드 환경에서 하나의 스레드만 read&write하고 나머지 스레드가 read하는 상황에서 가장 최신의 값을 보장한다.
- CPU Cache 보다 비용이 더 큰 Main Memory에 저장되어 사용되기 때문에 성능에 영향을 줄 수 있다. 따라서 인스턴스의 캐싱을 고려해볼 수 있을 것 같다.
[reference]
Head First Design Patterns
http://tutorials.jenkov.com/java-concurrency/volatile.html