싱글턴 패턴을 이해한다.

Goal

  • 싱글턴 패턴의 개념을 이해한다.
  • 예시를 통해 싱글턴 패턴을 이해한다.

싱글턴 패턴이란

참고

예시

프린터 관리자 만들기

문제점

다중 스레드에서 Printer 클래스를 이용할 때 인스턴스가 1개 이상 생성되는 경우가 발생할 수 있다.

해결책

프린터 관리자(Lazy Initialization)는 사실 다중 스레드 애플리케이션이 아닌 경우에는 아무런 문제가 되지 않는다.

  1. 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법
    public class Printer {
       // static 변수에 외부에 제공할 자기 자신의 인스턴스를 만들어 초기화
       private static Printer printer = new Printer();
       private Printer() { }
       // 자기 자신의 인스턴스를 외부에 제공
       public static Printer getPrinter(){
         return printer;
       }
       public void print(String str) {
         System.out.println(str);
       }
    }
    
    • static 변수
      • 객체가 생성되기 전 클래스가 메모리에 로딩될 때 만들어져 초기화가 한 번만 실행된다.
      • 프로그램 시작~종료까지 없어지지 않고 메모리에 계속 상주하며 클래스에서 생성된 모든 객체에서 참조할 수 있다.
  2. 인스턴스를 만드는 메서드에 동기화하는 방법
    public class Printer {
       // 외부에 제공할 자기 자신의 인스턴스
       private static Printer printer = null;
       private int counter = 0;
       private Printer() { }
       // 인스턴스를 만드는 메서드 동기화 (임계 구역)
       public synchronized static Printer getPrinter(){
         if (printer == null) {
           printer = new Printer(); // Printer 인스턴스 생성
         }
         return printer;
       }
       public void print(String str) {
         // 오직 하나의 스레드만 접근을 허용함 (임계 구역)
         // 성능을 위해 필요한 부분만을 임계 구역으로 설정한다.
         synchronized(this) {
           counter++;
           System.out.println(str + counter);
         }
       }
    }
    
    • 인스턴스를 만드는 메서드를 임계 구역으로 변경
      • 다중 스레드 환경에서 동시에 여러 스레드가 getPrinter 메서드를 소유하는 객체에 접근하는 것을 방지한다.
    • 공유 변수에 접근하는 부분을 임계 구역으로 변경
      • 여러 개의 스레드가 하나뿐인 counter 변수 값에 동시에 접근해 갱신하는 것을 방지한다.
    • getInstance()에 Lock을 하는 방식이라 속도가 느리다.

정적 클래스

정적 메서드로만 이루어진 정적 클래스를 사용하면 싱글턴과 동일한 효과를 얻을 수 있다.

public class Printer {
      private static int counter = 0;
      // 메서드 동기화 (임계 구역)
      public synchronized static void print(String str) {
        counter++;
        System.out.println(str + counter);
      }
}
public class UserThread extends Thread{
    // 스레드 생성
    public UserThread(String name) { super(name); }
    // 현재 스레드 이름 출력
    public void run() {
      Printer.print(Thread.currentThread().getName());
    }
}
public class Client {
    private static final int THREAD_NUM = 5;
    public static void main(String[] args) {
      UserThread[] user = new UserThread[THREAD_NUM];
      for (int i = 0; i < THREAD_NUM; i++) {
        // UserThread 인스턴스 생성
        user[i] = new UserThread((i+1));
        user[i].start();
      }
    }
}

Enum 클래스

public enum SingletonTest {
	INSTANCE;
  
	public static SingletonTest getInstance() {		
		return INSTANCE;
	}
}

관련된 Post

References