2018년 10월 9일 화요일

java Singloeton design pattern

디자인 패턴 강의를 다른 멘토분과 함께 나누어서 진행중이며, 기초적인 자바 디자인 패턴을 파트별로 나누어 수업중입니다.
이중에 제가 맡은 파트들을 하나씩 정리 해 볼 생각입니다.
이 글은 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를 호출한다고 해서 무조건 적으로 동기화 블럭에 갇히지 않기 때문에, 리소스 소모 문제가 해결되겠네요.




댓글 1개: