Design pattern

[Design Pattern] Singleton Pattern

s00ng 2022. 7. 20. 18:12
1. 싱글턴 패턴 정의
인스턴스가 오직 하나만 생성되는 것을 보장하고, 어디에서든 이 인스턴스에 접근할 수 있도록 하는 디자인 패턴
  • 하나의 인스턴스만을 생성하는 책임 있음
  • getInstance 메서드를 통해 모든 클라이언트에게 동일한 인스턴스 반환하는 작업 수행

싱글턴 패턴 컬레보레이션

 

2. 싱글턴 패턴 구현

- 생성자의 접근 지정자 private으로 설정

- getter를 통해 인스턴스를 제공

 

다중 스레드 애플리케이션에서 (싱글톤 구현시) 발생하는 문제

경합 조건(race condition) : 메모리와 같은 동일한 자원을 2개 이상의 스레드가 이용하려고 경합하려는 현상

※ 해결 방법  

1. 정적 변수에 인스턴스를 만들어 바로 초기화 (Eager Initialization)
2. 인스턴스를 만드는 메서드에 동기화 (Thread-Safe 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 변수 값에 동시에 접근해 갱신하는 것을 방지

 

3. 정적 클래스 (static class)
정적 메서드로만 이루어진 정적 클래스를 사용하면 싱글턴과 동일한 효과를 얻을 수 있음
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();
      }
    }
}

 

 

싱글톤 패턴과의 차이점

- 정적 클래스를 이용하면 객체를 전혀 생성하지 않고 메서드를 사용한다.
- 정적 메서드를 사용하므로 일반적으로 실행할 때 바인딩되는(컴파일 타임에 바인딩되는) 인스턴스 메서드를 사용하는 것보다 성능 면에서 우수

 

정적 클래스를 사용할 수 없는 경우

- 인터페이스를 구현해야 하는 경우, 정적 메서드는 인터페이스에서 사용할 수 없음