[펌&수정] - Singleton Pattern-1 (싱글턴 패턴)
Singleton Pattern
Singleton 패턴은 흔히 사용되는 생성 패턴의 하나이다.
이는 하나의 클래스에서 오직 하나의 인스턴스만 생성되도록 보장하는 기법을 기술한다.
즉, 클래스 외부의 누구도 객체의 인스턴스를 생성하지 못하게 하는 접근법을 사용한다.
이 접근법은 다양한 방식으로 구현이 가능하다.
아래는 가장 대표적인 구현 방식이다.
아마 현장에서도 가장 많이 쓰이고 있는 패턴 중 하나가 아닐까 싶습니다.
생성자를 private으로 선언하여 다른 클래스에서 이 클래스의 객체를 직접 생성시키지 못하게 하고, static 으로 선언 된 자기 자신의 객체를 넘겨주는 메소드를 만들어 이미 생성되어 있는 자기 자신의 객체를 넘겨주는 방식입니다.
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를 사용하는 것 입니다.
if(instance == null) {
instance = new Singleton();
}
}
메소드 자체에 동기화가 걸려버리는거죠.. 하지만 위의 경우.. 멀티스레드로 인해 문제가 생기는 경우는 instance가 처음 생성 될 때 뿐입니다. 즉, 일단 instance 변수에 Singleton의 객체 레퍼런스를 대입하고 나면 이 메소드를 통채로 동기화를 시킬 필요는 없는 것입니다.
괜히 오버헤드만 증가하죠..
그래서 나온 다른 하나의 방법은 아예 처음부터 만들어버리는 것입니다.
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을 이용하면
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 를 사용하셔도 동기화 문제는 처리 하실 수 있습니다.
//////////////////////////////////////// 참고 ////////////////////////////////////////
인스턴스 생성 후 다시 인스턴스를 생성했을 때의 비교
싱글턴을 사용한 경우(첫번째 방식)
[04:51:47]::>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[04:51:47]::>1 : 22130853
[04:51:47]::>2 : registration.entrance.PdssMgr@151b0a5
[04:51:47]::>3 : class registration.entrance.PdssMgr
[04:51:47]::>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
[04:51:47]::>1 : 22130853
[04:51:47]::>2 : registration.entrance.PdssMgr@151b0a5
[04:51:47]::>3 : class registration.entrance.PdssMgr
싱글턴을 사용하지 않은 경우
[04:51:47]::>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
[04:51:47]::>1 : 3974996
[04:51:47]::>2 : registration.entrance.PdssMgrTest@3ca754
[04:51:47]::>3 : class registration.entrance.PdssMgrTest
[04:51:47]::>DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
[04:51:47]::>1 : 896033
[04:51:47]::>2 : registration.entrance.PdssMgrTest@dac21
[04:51:47]::>3 : class registration.entrance.PdssMgrTest
[출처(참고)] : http://devyongsik.tistory.com/tag/singleton