CHAPTER 05.싱글톤 패턴
읽은 책 정리/Head Firstr Design Pattern

CHAPTER 05.싱글톤 패턴

1.싱글톤 패턴이란

  • 싱글톤 패턴은 클래스 인스턴스를 하나만 만들고, 하나만 만든 인스턴스로의 전역 접근을 제공하는 패턴입니다.

싱글톤 패턴 구현

public class Singleton {
	private static Singleton uniqueInstance;
 
	// other useful instance variables here
 
	private Singleton() {}
 
	public static  Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
 
	// other useful methods here

}
  • 코드를 살펴보겠습니다.
  • 먼저 Singleton 클래스의 하나뿐인 인스턴스를 저장하는 정적 변수를 선언합니다.
  • 생성자는 private로 선언하여 외부에서 생성 불가능하도록 선언하여 내부에서만 생성 가능하게 합니다. 이를 통해 하나의 인스턴만 생성할 수 있도록 유도합니다.
  • getInstance() 메소드는 클래스의 인스턴스를 리턴합니다.

2.싱글톤 패턴 이해를 위한 간단한 문제 제안

  • 멀티스레딩 상황에서 만약 두 스레드가 위의 코드를 실행한다고 가정해보겠습니다.
  • 만약 운이 안좋게도 if문 안에 두 스레드가 동시에 들어가고 1개의 스레드가 먼저 return을 실행하게 될 경우에는 싱글톤 패턴이 다른 객체를 반환하는 문제가 발생하게 됩니다.
public class Singleton {
	private static Singleton uniqueInstance;
 
	// other useful instance variables here
 
	private Singleton() {}
 
	public static  Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton(); <--- 1번 스레드
		}
		return uniqueInstance; <--- 2번 스레드
	}
 
	// other useful methods here

}

4.synchrnoized를 사용해서 문제 해결하기

public class Singleton {
	private static Singleton uniqueInstance;
 
	// other useful instance variables here
 
	private Singleton() {}
 
	public static synchronized Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
 
	// other useful methods here
	public String getDescription() {
		return "I'm a thread safe Singleton!";
	}
}
  • getInstance() 메소드 앞에 synchronized를 선언하면 간단하게 문제가 해결됩니다.
  • 하지만 이렇게 메소드에 synchronized를 선언할 경우에는 성능이 100배 정도 저하가 됩니다.
  • 또한 synchronized가 필요한 시점은 메소드가 시작되는 때뿐이기 때문에 uniqueInstance 변수에 Singlton 인스턴스를 대입하면 굳이 메소드를 동기화한 상태로 유지할 필요가 없습니다. 현재 메소드에 synchronized를 선언한 상태는 처음을 제외하고는(객체를 하나만 안전하게 생성할 때) 불필요한 오버헤드만 증가시킬뿐입니다.

5.처음부터 인스턴스를 생성해서 문제 해결하기

public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		return uniqueInstance;
	}
	
	// other useful methods here
	public String getDescription() {
		return "I'm a statically initialized Singleton!";
	}
}
  • 위의 아이디어를 통해서 변경해본 코드입니다.
  • uniqueInstance에 Singlton을 선언하였습니다.
  • 그러나 위와 같은 방식은 전역 변수처럼 사용하지 않는데도 서비스가 시작할 때 미리 만들어서 사용하기 때문에 효율성 측면에서 단점이 존재할 수 있습니다.

6.DCL을 사용해서 문제 해결하기

  • DCL(Double-checked-Locking)을 사용하여 싱글톤 인스턴스를 생성할 경우 멀티스레딩 환경에서 발생하는 문제를 해결하고 나아가 인스턴스가 필요할 때만 생성되게 하여 효율성 측면의 문제점도 해결하는 방식입니다.
public class Singleton {
	private volatile static Singleton uniqueInstance;
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			synchronized (Singleton.class) { //Singlton.class는 동기화 처리하겠다, 그러나 메소드 선언 방식돠는 다르게 처음에만 동기화된다.
				if (uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}
  • 필드 선언부분에 volatile을 사용한 것을 확인할 수 있습니다.
  • volatile을 사용하면 멀티스레딩을 쓰더라도 uniqueInstance 변수가 Singletone 인스턴스로 초기화되는 과정이 올바르게 진행됩니다.
💡
DCL은 Java5 보다 낮은 버전의 JVM 버전에서는 사용할 수 없습니다. (근데.. 2023년 기준 1.4 버전을 사용하는데가 있을까요?)

정리

  • 애플리케이션에서 특정 클래스가 하나만 있어야 한다면 해당 클래스를 싱글턴으로 만들면 됩니다.
  • 싱글턴 패턴을 사용하면 하나뿐인 인스턴스를 어디서나 접근할 수 있습니다.
    • 전역변수와의 차이점이 뭐냐라는 의문점이 들수 있는데 생성 시점과 효율성 측면에서 좀 더 우세합니다.
  • 멀티스레딩 환경을 고려해서 사용해야합니다.
  • 클래스 로더가 2개가 될 경우에는 문제가 발생합니다.
    • 클래스마다 서로 다른 네임스페이스를 정의하기 때문에 클래스 로더가 2개 이상이라면 같은 클래스를 여러번 로딩할 수 있기 때문입니다.
    • 이렇게 될경우 하나만 존재하는 싱글톤 패턴의 정의가 무너집니다.
    • 그렇기 때문에 클래스로 로더를 한 개 이상 사용할 경우 클래스 로더를 직접 지정해서 사용하는 등 고려해야 할 사항이 추가 됩니다.