KyungHwan's etc.

자바(JAVA) 디자인패턴(Design pattern) 싱글톤(Singleton)패턴 본문

Java/디자인패턴(Design pattern)

자바(JAVA) 디자인패턴(Design pattern) 싱글톤(Singleton)패턴

KyungHwan_0 2018. 6. 22. 16:02

자바(JAVA) 디자인패턴(Design pattern) 싱글톤(Singleton)패턴

자바 디자인패턴중 가장 익숙한 패턴중 하나인 싱글톤 패턴이다.

자바에 프레임워크중 가장 유명한 프레임워크인 스프링프레임워크(Spring Framework)에서 스프링컨테이너를 만들때 보통 사용되는 디자인패턴이다.

말 그대로 하나의 객체를 만들어서 새로 생성하지 않고 인스턴스를 사용한다는 점에서 메모리를 효율적으로 쓸수있고, new 연산을 통해 계속 만들어서 사용하지 않기 때문에 성능 또한 굉장히 빠르다는 장점이 있다.

  • 정의

    • 자주 사용 하는 클래스 기능 사용 시 메모리에 객체를 생성하여 사용하면 비율적이다.

    • 클래스의 객체를 미리 메모리에 생성해 놓고 사용한다

    • 1개의 객체만 메모리에 존재하게 한다.



  • 싱글톤패턴 구현방법

    • 프로그램 내에서 단 1개의 인스턴스만 생성되게 한다.

    • 생성된 인스터스는 getInstance()통해 접근한다

    • 인스턴스를 생성할 클래스이의 생성자와 변수는 private로 선언한다

    • 인스턴스에 접근할 메서드는 public으로 선언한다.

    • 외부 클래스에서는 new를 통한 인스턴스 생성은 불허한다.




싱글톤 클래스

public class MySingleton {
   private static MySingleton mySingleton = null;
   
   private MySingleton(){
       System.out.println("MySingleton 인스턴스 생성");
  }
}

public static MySingleton getInstance(){
   if(mySingleton ==null){
       mySingleton = new MySingleton();
  }
   return mySingleton;
}

2행에서 mySingleton 변수를 static 으로 선언하고 있다.

4행에서는 private로 지정하여 외부에서 호출할 수 없게 한다.

8~13행의 getInstance()는 public으로 지정되어 있어서 외부에서 호출 시 MySingleton 객체가 생성되지 않았다면 생성 한후 리턴한다.

싱글톤 사용 예제

public class SingletonTest {
   public static void main(String[]args) {
       System.out.println("첫 번째 getInstance() 호출");
       MySingleton a = MySingleton.getInstance();
       //MySingleton a = new MySingleton();
       System.out.println("두 번째 getInstance() 호출");
       MySingleton b =  MySingleton.getInstance();
 
   if(a==b){
       System.out.println("두 싱글톤은 동일한 객체입니다");
  }
  }
}

실행결과
--------------------------------------------------
첫 번째 getInstance() 호출
MySingleton 인스턴스 생성
첫 번째 getInstance() 호출
두 싱글톤은 동일한 객체입니다

4행에서 최초로 MySingleton a = MySingleton.getInstance(); 메소드를 수행하여 getInstance()호출하면, mySingleton 변수에 MySingleton 객체를 할당한다. 그리고 그 결과를 리턴하면 변수 a도 역시 MySingleton객체를 가리킨다.

그리고 싱글톤 클래스에 있는 "MySingleton 인스턴스 생성" 가 출력된다.

하지만 두번째 MySingleton b = MySingleton.getInstance(); 메소드를 수행하면, MySingleton 인스턴스 생성" 가 출력되지 않는다.

싱글톤 패턴에서 if문으로 null검사하여 null이면 return 값으로 기존의 MySingleton 위치를 리턴한다.

즉, MySingleton은 메모리에 1개만 생성되어 사용된다.


싱글톤패턴을 간단하게 정리하자면

private static 로 선언된 변수에 객체를 할당하여, 그 변수로만 인스턴스를 활용하는 것을 말한다.

하지만 프로그램의 크기가 커져서 수 많은 클래스에서 위와 같은 singleton pattern을 사용한다고 가정해보자. mySingleton = new MySingleton();으로 인해 클래스가 load 되는 시점에 인스턴스를 생성시키는데 이마저도 부담스러울 수가 있다.

또한 이 소스는 MySingleton 클래스가 인스턴스화 되는 시점에 어떠한 에러처리도 할 수가 없다.

lazy initialization

이제 클래스 인스턴스가 사용되는 시점에 인스턴스를 만드는 singleton pattern을 배워보도록 하자. 아래 소스의 lazy initialization pattern은 필요할때 인스턴스를 생성시키는 것이 핵심이다.

public class LazyInitialization {

private static LazyInitialization instance;
private LazyInitialization () {}

public static LazyInitialization getInstance () {
if ( instance == null )
instance = new LazyInitialization();
return instance;
}

public void print () {
System.out.println("It's print() method in LazyInitialization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}
}

new LazyInitialization(); 가 어디에 선언되었는지 주목해보자.

getInstance() method 안에서 사용되었다. if문을 이용해 instance가 null 인 경우에만 new를 사용해 객체를 생성하였다.

최초 사용시점에만 인스턴스화 시키기 때문에 프로그램이 메모리에 적재되는 시점에 부담이 많이 줄게된다. 하지만 여전히 문제는 남아있다.

만약 프로그램이 muilti thread 방식이라면 위와 같은 singleton pattern은 안전하지 않다. 동일 시점에 getInstance() method를 호출하면 인스턴스가 두번 생길 위험이 있다.

thread safe initalization

위에서 문제가 되었던 muilit thread문제를 해결하기 위해 synchronized(동기화)를 사용하여 singleton pattern을 구현한다. 여러 thread들이 동시에 접근해서 인스턴스를 생성시키는 위험은 없어졌다. 하지만 수 많은 thread 들이 getInstance() method 를 호출하게 되면 높은 cost 비용으로 인해 프로그램 전반에 성능저하가 일어난다.

public class ThreadSafeInitalization {

private static ThreadSafeInitalization instance;
private ThreadSafeInitalization () {}

public static synchronized ThreadSafeInitalization getInstance () {
if (instance == null)
instance = new ThreadSafeInitalization();
return instance;
}

public void print () {
System.out.println("It's print() method in ThreadSafeInitalization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}

}


initialization on demand holder idiom

미국 메릴랜드 대학의 컴퓨터 과학 연구원인 Bill pugh 가 기존의 java singleton pattern이 가지고 있는 문제들을 해결 하기 위해 새로운 singleton pattern을 제시하였다. Initialization on demand holder idiom기법이다. 이것은 jvm 의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결한다.

public class InitializationOnDemandHolderIdiom {

private InitializationOnDemandHolderIdiom () {}
private static class Singleton {
private static final InitializationOnDemandHolderIdiom instance = new InitializationOnDemandHolderIdiom();
}

public static InitializationOnDemandHolderIdiom getInstance () {
System.out.println("create instance");
return Singleton.instance;
}
}

initialization on demand holder idiom 역시 lazy initialization이 가능하며 모든 java 버젼과, jvm에서 사용이 가능하다. 현재 java 에서 singleton 을 생성시킨다고 하면 거의 위의 방법을 사용한다고 보면 된다.

Reference

https://blog.seotory.com/post/2016/03/java-singleton-pattern

저 초보자를위한 JAVA Programing (이병승 지음)

Comments