본문 바로가기
스터디/헤드퍼스트 디자인패턴

[헤드퍼스트 디자인패턴] 04. 싱글턴 패턴(Singleton Pattern)

by 디토20 2022. 6. 18.
반응형

 

 

[헤드퍼스트 디자인패턴] 04. 싱글턴 패턴(Singleton Pattern)

 

1. 싱글턴 패턴이란?

클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.

 

 

2. 고전적인 싱글턴 패턴 구현법

public class Singleton {
	private static Singleton uniqueInstance;
    
    // 기타 인스턴스 변수
    
    private Singleton() {}
    
    public static Singleton getInstance() {
    	if(uniqueInstance == null) {
        	uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    
	// 기타 메소드

}

 

  • 생성자는 private 이므로 외부에서 new 키워드를 통해 객체를 생성할 수 없다.
  • 객체를 생성하기 위해서는 Singleton.getInstance() 라는 정적 메소드를 통해서만 생성 가능
  • 이미 생성된 객체가 있을 경우, 기존 객체를 반환하고 없을 경우 생성 후 반환 한다.

 

2.1 고전적인 싱글턴 패턴의 문제점

멀티쓰레드일 경우, 여러 싱글톤 객체가 생성 될 수 있다.

 

 

 

3. 동시성 문제 해결 방법

3.1 Synchronized 키워드 이용

public class Singleton {
	private static Singleton uniqueInstance;
    
    // 기타 인스턴스 변수
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
    	if(uniqueInstance == null) {
        	uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    
	// 기타 메소드

}

 

  • synchronized : 한번에 하나의 쓰레드만 실행할 수 있기 때문에 동시성 해결이 가능
  • 그러나 동기화가 필요한 시점은 첫 인스턴스 생성 시점이기 때문에 인스턴스가 생성된 후에는 동기화를 유지할 필요가 없으나 위의 코드의 경우 모든 순간 마다 동기화를 하게 된다. -> 불필요한 오버헤드 증가로 인한 속도 저하
  • getInstance() 메소드가 애플리케이션에 큰 부담을 주지 않는다면 그냥 둬도 좋으나, 병목으로 작용할 경우 개선 필요

 

 

3.2 처음부터 Singelton 인스턴스 생성

public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
    
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        return uniqueInstance;
    }
    
	// 기타 메소드

}

 

  • 클래스가 로딩될 때 JVM에서 Singleton 인스턴스 생성
  • synchronized의 속도 저하 문제 해결
  • 애플리케이션 시작 시점에 인스턴스가 생성되기 때문에, 사용하지 않을 경우 자원의 낭비가 된다.

 

 

3.3 DCL(Double-Checked Loking) 사용

public class Singleton {
	private volatile static Singleton uniqueInstance;
    
    // 기타 인스턴스 변수
    
    private Singleton() {}
    
    public static Singleton getInstance() {
      if(uniqueInstance == null) {
        synchronized (Signleton.class) {
          if(uniqueInstance == null) {
            uniqueInstance = new Singleton();
          }
        }
      }
        return uniqueInstance;
    }
    
	// 기타 메소드

}

 

  • uniqueInstance가 null일 경우만 synchronized 적용
  • 객체가 생성된 후에는 synchronized 체크를 하지 않으므로 속도 저하 문제 해결

 

volatile란?
  • Multi Thread 환경에서 변수 값을 Read 할 때 CPU cache가 아닌 Main Memory에서 값을 읽고 쓰는 것
  • Mutli Thread 어플리케이션에서는 성능 향상을 위해 CPU cache를 사용하는데, 이때 발생하는 변수값 불일치 문제를 해결할 수 있다.
  • cache를 이용하지 않는 만큼 성능에 영향을 줄 수 있으니, 꼭 필요한 경우만 사용할 것

 

 

4. static method를 이용한 Singleton 구현 시 발생 문제

4.1 직렬화와 역직렬화 (Serialization & Deserialization)

  • 클래스를 역직렬화할 때 새 인스턴스가 생성되어 싱글턴 속성을 위반 -> readResolver 메소드 구현으로 해결 가능

4.2 리플렉션 (Reflectiom)

  • 리플렉션을 이용하면 런타임에 private 생성자에 접근하여 새로운 인스턴스를 생성할 수 있다.

 

 

 

5. enum class 사용

public enum SingletonEnum {
    INSTANCE;
    int value;
    
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}
public class EnumDemo {
    
    public static void main(String[] args) {
        SingletonEnum singleton = SingletonEnum.INSTANCE;
        
        System.out.println(singleton.getValue());
        singleton.setValue(2);
        System.out.println(singleton.getValue());
    }
}

 

  • enum 인스턴스의 생성은 기본적으로 thread safe
  • enum 타입은 기본적으로 직렬화 가능하므로 Serializable 인터페이스를 구현할 필요가 없고, 리플렉션 문제도 발생하지 않는다.
  • 인스턴스가 JVM 내에 하나만 존재한다는 것이 100% 보장 되므로, Java에서 싱글톤을 만드는 가장 좋은 방법으로 권장된다.

 

 

6. 싱글턴 패턴 정리

  • 어떤 클래스에 싱글턴 패턴을 적용하면 그 클래스의 인스턴스가 1개만 있도록 할 수 있다.
  • 싱글턴 패턴을 사용하면 하나뿐인 인스턴스를 어디서든지 접근 할 수 있다.
  • 멀티 스레드를 사용하는 애플리케이션에서는 속도와 자원 문제를 파악하고 적절한 구현법을 사용해야 한다.
  • 자바의 enum을 쓰면 간단하게 싱글턴을 구현할 수 있다.

 

728x90
반응형

댓글