suyeonme

[디자인 패턴] 싱글톤 패턴(Singleton Pattern) 본문

프로그래밍👩🏻‍💻/디자인 패턴

[디자인 패턴] 싱글톤 패턴(Singleton Pattern)

suyeonme 2022. 7. 4. 22:26
헤드 퍼스트 디자인 패턴을 읽고 정리한 내용입니다.

1. 싱글톤 패턴(Singleton Pattern)이란?

  • 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공하는 디자인 패턴이다.
  • 스레드 풀, 캐시, 대화상자, 사용자 설정, 레지스트리 설정을 처리하는 객체, 로그 기록용 객체, 디바이스 드라이버등에 주로 사용된다.

1.1 전역 변수 vs 싱글톤

  • 전역 변수에 객체를 대입하면, 애플리케이션이 시작될 때 객체가 생성된다. 만약 객체가 자원을 많이 사용할 경우, 그리고 해당 객체가 사용되지 않으면 불필요하게 자원을 낭비할 수 있다.

2. 멀티스레딩을 고려하지 않은 싱글톤

  • 간단하지만, 멀티스레드 환경에서는 문제가 발생할 수 있다.
  • 생성자를 private으로 선언했으므로 Singleton에서만 클래스의 인스턴스를 생성할 수 있다.
public class Singleton {
  private static Singleton uniqueInstance;
  
  private Singleton() {}
  
  public static Singleton getInstance() {
    if(uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
}

3. 멀티스레딩을 고려한 싱글톤(동기화)

동기화(synchronize)란?

여러 개의 스레드가 한 개의 자원을 사용하고자 할 때 해당 스레드만 제외하고 나머지는 접근을 못하도록 막는 것이다.

 

동기화(synchronize)가 필요한 경우

  • 하나의 객체를 여러 스레드에서 동시에 사용할 경우
  • static으로 선언한 객체를 여러 스레드에서 동시에 사용할 경우

3.1 synchronized 사용

  • synchronized 키워드를 사용하면 한 스레드가 메소드 사용을 끝내기전까지 다른 스레드는 기다려야한다.
  • synchronized는 성능 저하를 발생시킨다는 단점이 있다. 
public class Singleton {
  private static Singleton uniqueInstance;
  
  private Singleton() {}
  
  public static synchronized Singleton getInstance() {
    if(uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
}

3.2 인스턴스를 만들어서 반환

  • 클래스가 로딩될 때 JVM에서 Singleton의 하나뿐인 인스턴스를 생성한다.
  • JVM에서 인스턴스를 생성하기 전까지 어떤 스레드도 uniqueInstance 정적 변수에 접근할 수 없다.
public class Singleton {
  private static Singleton uniqueInstance = new Singleton();
  
  private Singleton() {}
  
  public static Singleton getInstance() {
	return uniqueInstance;
  }
}

3.3 DCL(Double-Checked Locking) 사용

volatile 키워드

  • CPU chach 메모리가 아닌 메인 메모리(RAM)에 값을 read하고 write한다. 따라서  다른 스레드라도 같은 메모리 주소를 참조한다.
  • 멀티스레드 환경에서 하나의 스레드만 read, write하고, 다른 스레드는 onlyread가 보장되는 경우 사용한다. 즉 여러개의 스레드가 write하는 상황이라면 race condition을 해결할 수 없다.
  • volatile로 선언된 변수가 있는 코드는 최적화되지 않는다.

volatile키워드를 사용하여 싱글톤 구현

  • 인스턴스가 생성되어 있는지 확인 후, 생성되어 있지 않을 때만 동기화할 수 있다.
  • volatile 키워드 사용시, 멀티스레딩 환경에서도 uniqueInstance 변수가 Singleton 인스턴스로 초기화되는 과정이 올바르게 진행된다.
  • DCL은 자바 5 이상 버전에서 사용할 수 있다. 
public class Singleton {
  private volatile static Singleton uniqueInstance;
  
  private Singleton() {}
  
  public static Singleton getInstance() {
    if(uniqueInstance == null) {
      synchronized (Singleton.class) {
        if(uniqueInstance == null) {
          uniqueInstance = new Singleton();
        }
      }
    }
  }
}

3.4 enum 사용

 직렬화, 역직렬화 (Serialization, Deserialization)란?

  • 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술(직렬화)과 바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)
  • 클래스를 역직렬화할 때 새로운 인스턴스가 생성되어 싱글톤 속성을 위반한다. (직렬화할 때 readObject()를 구현해야하는데, readObject()는 매번 새로운 인스턴스를 반환한다.)

Reflection이란?

  • 구체적인 클래스 타입을 몰라도 해당 클래스의 메서드, 타입, 변수등에 접근할 수 있도록 해주는 자바 API이다. (컴파일타임이 아닌 런타임에 동적으로 특정한 클래스의 정보를 추출할 수 있도록 하는 프로그래밍 기법)
  • 리플렉션을 이용하면 런타임에 private 생성자에 접근하여 새로운 인스턴스를 생성할 수 있게 되어 싱글톤 속성을 위반한다.

 

enum을 사용하여 싱글톤 구현

  • enum을 사용하여 싱글톤을 구현하면, 동기화 문제, 클래스 로딩 문제, 리플렉션, 직렬화/역직렬화 문제를 해결할 수 있다.
  • enum은 기본적으로 직렬화가 가능하므로 Serializable 인터페이스를 구현할 필요가 없다. 
  • 생성자가 private으로 제한되므로 외부에서의 인스턴스 생성을 제한한다.
  • 인스턴스가 JVM내에 하나만 존재한다는 것이 100% 보장된다. 따라서 가장 추천되는 방법이다.
public enum Singleton {
  UNIQUE_INSTANCE;
}

public class SingletonClient {
  public static void main(String[] args) {
    Singleton singleton = Singleton.UNIQUE_INSTANCE;
  }
}
 
Comments