너무나 잘 알려진 패턴입니다.
단 하나의 인스턴스를 유지하는 것입니다.
아마 현장에서도 가장 많이 쓰이고 있는 패턴 중 하나가 아닐까 싶습니다.
생성자를 private으로 선언하여 다른 클래스에서 이 클래스의 객체를 직접 생성시키지 못 하게
하고, static 으로 선언 된 자기 자신의 객체를 넘겨주는 메소드를 만들어
이미 생성되어 있는 자기 자신의 객체를 넘겨주는 방식입니다.
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
일반적으로 사용되는 싱글턴 패턴의 형식입니다.
getInstance 메서드는 Singleton 인스턴스가 있으면 그것을 리턴하고
없으면, 새로 생성하여 리턴합니다.
하지만, 위와 같은 경우에는 문제가 생길 수 있습니다.
다수의 스레드가 getInstacne 메소드를 실행하는 경우
instance 객체가 2개 이상 생성 될 수 있기 때문입니다.
예를 들어 쓰레드 A와 B가 getInstance 메소드를 거의 동시에 실행 되었을 경우
jvm의 스케쥴링에 따라서 어떤 경우에는 아래와 같은 현상이 발생 할 수 있습니다.
쓰레드 A가 instance == null 임을 확인
쓰레드 A wait
쓰레드 B가 instance == null 임을 확인
쓰레드 B가 instance = new Singleton(); 를 실행
쓰레드 B가 return instance로 객체를 리턴.
쓰레드 A가 instance = new Singleton(); 를 실행
쓰레드 A가 return instance로 객체를 리턴.
위 처럼 서로 다른 객체가 리턴 되는 경우가 있을 수 있습니다.
이런 멀티스레딩을 해결 하는 간단한 방법 중 하나는 synchronized를 사용하는 것 입니다.
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
메소드 자체에 동기화가 걸려버리는거죠.. 하지만 위의 경우.. 멀티스레드로 인해 문제가 생기는 경우는 instance가 처음 생성 될 때 뿐입니다. 즉, 일단 instance 변수에 Singleton의 객체 레퍼런스를 대입하고 나면 이 메소드를 통채로 동기화를 시킬 필요는 없는 것입니다.if(instance == null) {
instance = new Singleton();
}
괜히 오버헤드만 증가하죠..
그래서 나온 다른 하나의 방법은 아예 처음부터 만들어버리는 것입니다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
그리고 다른 하나의 방법은 volatile 을 사용하는 것입니다.
책에서는 DCL이라고 하네요. Double-Checking Locking.
상세한 내용은
http://javaservice.net/~java/bbs/read.cgi?m=qna&b=qna2&c=r_p_p&n=1088474804
이곳에 서민구(4baf)님께서 작성하신 댓글을 읽어보시면 좋을 것 같습니다.
아무튼.. 이 volatile을 이용하면
public class Singleton {
private volatile static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
private volatile static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile을 사용했음에도 synchronized(Singleton.class)를 사용한 이유는
안전장치라고 생각해도 될 것 같습니다.
일단 한번 인스턴스가 생성 된 이후에는 첫번째 if(instance == null) { } 이 블럭 안으로
쓰레드가 들어 갈 일이 없을테니까요.
메서드 전체에 syncronized를 걸어놓지 않았기 때문에 일단 처음 instance 변수에
Singleton 객체 레퍼런스를 대입 할 때를 제외하고는 성능의 저하도 없을 것입니다.
다만 이 방식은 자바5 부터 사용 하시길 권장합니다.
그리고 속도의 문제가 큰 이슈거리가 아니시라면 그냥 메소드 전체에
synchronized 를 사용하셔도 동기화 문제는 처리 하실 수 있습니다.