디자인 패턴 강의를 다른 멘토분과 함께 나누어서 진행중이며, 기초적인 자바 디자인 패턴을 파트별로 나누어 수업중입니다.
이중에 제가 맡은 파트들을 하나씩 정리 해 볼 생각입니다.
이 글은 Head first Design patterns 를 상당히 참조하여 쓰인 글 입니다.
Singleton.
싱글턴 혹은 싱글톤 이라고 부릅니다.
이패턴은 단! 하나! 의 클래스 인스턴스만을 편하게 사용하고 관리하게 하기 위해 고안된 방법입니다.
비단 자바뿐만 아니라 모든 언어에서도 상당히 많이 쓰이는 페턴이며, 지금 이 글을 읽는 여러분들도 알게 모르게 사용해 보신 경험이 있을지도 모릅니다.
이제 한번 자세히 알아봅시다.
Jay : MyClass 를 사용하기 위해서는 어떻게 쓸까요?
Bob : new MyClass() 로 객체를 인스턴스로 생성하여 사용합니다.
Jay : 다른 객체에서 해당 클래스를 사용할 수 있는가요?
Bob : 위와 동일한 방법으로 가능합니다.
Jay : 만약 public이 아니라면?
Bob : protected 라고 할 경우, 같은 패키지 내에 있는 클래스들만이 호출이 가능하며,
private 경우는 본인만 가능합니다.
Jay : 그럼 이 코드는 어떨까요
class MyClass{
private MyClass(){}
}
Bob : 위와 같은 문맥으로, 본인 클래스만 생성 가능한 클래스 입니다.
Jay : 이 코드에 대해서 설명 해 보자면요? 외부에서 해당 메소드를 어떻게 호출하나요?
class MyClass{
public static void getInstance(){}
}
Bob : MyClass.getInstance() 왜나하면 static이기 때문에, MyClass 객체를 생성하지 않이도 이미 존재하는 메소드 입니다.
Jay : 이렇게 하면 어떨까요, 외부 클래스에서getInstance를 호출하면 무슨일이 일어나죠?
class MyClass{
private MyClass(){}
public static MyClass getInstance(){ return new MyClass();}
}
Bob : 오! MyClass.getInstance()를 호출하게 될 경우,
MyClass 객체 인스턴스를 가져올 수 있습니다.
Jay : 코드로 보겠습니다.
class MySingleton {
private MySingleton(){
syso("MySingleton 객체 생성");
}
public static MySingleton getInstance(){
return new MySingleton();
}
}
class SingletonRunner {
public static void main(){
MySingleton.getInstance();
}
}
output : MySingleton 객체 생성
Bob : 완벽하군요, 그래서 뭐 어쩌라구요?
Jay : 흥분하지 말고 들어보세요, 이제 위 코드들을 조금 수정해 보겠습니다
class MySingleton {
private static MySingleton myInstance = null;
private MySingleton(){
syso("MySingleton 객체 생성");
}
public static MySingleton getInstance(){
if( myInstance == null ) {
syso("생성 시작 MySingleton...");
return myInstance = new MySingleton();
}
syso("이미 MySingleton은 생성되어 있습니다.")
return myInstance;
}
}
class SingletonRunner {
public static void main(){
syso("mySingleton1 를 초기화 해 봅시당");
MySingleton mySingleton1 = MySingleton.getInstance();
syso("mySingleton2 를 초기화 해 봅시당");
MySingleton mySingleton2 = MySingleton.getInstance();
}
}output :
mySingleton1 를 초기화 해 봅시당
생성 시작 MySingleton...
MySingleton 객체 생성
mySingleton2 를 초기화 해 봅시당
이미 MySingleton은 생성되어 있습니다.
Bob :오 이런 세상에.. 클래스를 두번 생성시도했지만, 이미 만들어 져 있기 때문에 getInstance를 아무리 호출해도 같은 클래스만 가지고 오는군요!
Jay : 자 그럼, 이딴걸 왜, 어디에 쓸지 감이 좀 오나요?
Bob : 단 하나뿐인 클래스를 만들고, 다음에도 다른 클래스에서 해당 유일한 클래스를 사용 할 때에도, 복잡하게 인스턴스를 넘기지 않고, 간단하게 getInstance로 이미 만들어져 있는 클래스 정보를 얻어 올 수 있어요!. 그러면 우리는 MySingleton 클래스에 앱이 동작할때 필요한 고유하고 필수적인 정보( 이른바 DB연결 정보, 로그 따위의)를 중복 생성 걱정없이 안심하고 관리 할 수 있겠네요!
Jay : 세상에 잘도 아시는군요, 맞아요! 그럼 이건 문제점은 전혀 없을까요?
Bob : 물론 있워요! 이 싱글톤 패턴에 너무 의존하게 될 경우, 하나의 클래스가 혼자서 너무 많은 일을 맡게 될 수 있어요. oop 원칙중 하나인 단일 책임 원칙에 위배되는 등 으 문제가 발생할 수 도 있겠네요.
Jay : 쓰레드는 어떨까요? 쓰레드를 돌리면 문제는 없을까요?
Bob : 오..아마도..
Jay : 코드를 보시죠
class MySingleton {
private static MySingleton myInstance = null;
...}
class SingletonRunner {
public static void main(){
Thread th1 = new Thread(){
@override
public void run(){
super.run();
MySingleton mysingleton = MySingleton.getInstance();
}
};
Thread th2 = new Thread(){
@override
public void run(){
super.run();
MySingleton mysingleton = MySingleton.getInstance();
}
};
th1.start(); th2.start();
}
}output :
생성 시작 MySingleton...
MySingleton 객체 생성
생성 시작 MySingleton...
MySingleton 객체 생성
Bob : 물론 운이 좋아서 똑바로 작동 할 수 있겠지만, 보통은 output 의 로그처럼, 서로 myInstance가 없다고 생각하고 생성시켜 버릴 여지는 충분히 있군요. 이러면 쓸수가 없네요.
Jay : 다행히도 해결법은 존재한답니다.
스판
class MySingleton {
private static MySingleton myInstance = null;
private MySingleton(){
syso("MySingleton 객체 생성");
}
public static synchronized MySingleton getInstance(){
if( myInstance == null ) {
syso("생성 시작 MySingleton...");
return myInstance = new MySingleton();
}
syso("이미 MySingleton은 생성되어 있습니다.")
return myInstance;
}
}
class SingletonRunner {
public static void main(){
...
}
}
output :
생성 시작 MySingleton...
MySingleton 객체 생성
이미 MySingleton은 생성되어 있습니다
Jay : 하지만 synchronized 를 저렇게 사용하게 되면 기존의 getInstance 메소드와 비교하면 100배나 작동시간이 증가한다고 합니다.
Bob : 100배는 좀 그런데..물론 MySingleton 클래스의 역활이 별거 없다면 큰 차이는 없겠죠, 하지만, getInstance를 호출하면 항상 동기화를 하기때문에 계속해서
Jay : 네, 이게 만능은 아니겠죠!
또 MySingleton 의 필드값인 myInstance를 필드에서 생성하는 방식도 가능 할 거 같네요.
하지만 이 방식또한, 스레드 스케쥴링 부분에서 예상한 생성 시점과 다를 수 있다고도 하네요. 역시 만능은 아니라고 합니다.
그럼 또 다른방법을 알아봅니다..
class MySingleton {
private static volatile MySingleton myInstance = null;
private MySingleton(){
syso("MySingleton 객체 생성");
}
public static MySingleton getInstance(){
if( myInstance == null ) { // 먼저 생성유무를 따지고, 없는 경우엔
synchronized (MySingleton.class){ // 동기화블럭이 작동하며 생성 작업이 진행됩니다.
if( myInstance == null ) { //다시한번 생성유무를 따지기 때문에, 두번째로 시작된 스레드는 생성작업을 거치지 않고 끝납니다.
syso("생성 시작 MySingleton...");
return myInstance = new MySingleton();
}
syso("이미 MySingleton은 생성되어 있습니다.")
return myInstance;
}
}
}
class SingletonRunner {
public static void main(){
...
}
}
output :
생성 시작 MySingleton...
MySingleton 객체 생성
이미 MySingleton은 생성되어 있습니다
Bob : good! 좋아요, 이제 getInstance를 호출한다고 해서 무조건 적으로 동기화 블럭에 갇히지 않기 때문에, 리소스 소모 문제가 해결되겠네요.