5. Singleton Pattern

최정훈·2024년 11월 10일

1. Singleton Pattern의 필요성


프로그램을 실행하는데 있어서, 하나만 있어도 문제가 없는 객체들도 있다. Singleton Pattern을 사용하면, 전역변수를 사용할 때와 마찬가지로, 어디서든 객체 인스턴스에 접근할 수 있다. 전역변수를 사용할 때와 대비되는 장점이 있는데, 전역변수로 생성된 객체가 자원을 많이 잡아먹는다고 가정할 때, 그 객체가 사실 많이 호출되지 않는다면, 자원만 쓸데없이 낭비한 꼴이 될 것이다. 하지만, Singleton Pattern을 사용하면 필요할 때만 객체를 생성할 수 있다.

2. 고전적인 Singleton Pattern구현 방법


public class Singleton{
	private static Singleton uniqueInstance; //하나뿐인 인스턴스를 저장하는 정적 변수
	
	private Singleton(){} //생성자가 private로 설정되어있어서 Singleton에서만 객체 생성가능
	
	public static Singleton getInstance(){ //클래스의 인스턴스를 만들어서 리턴
		if(uniqueInstance == null){ //uniqueInstance가 null이 아니면, 이미 존재하면 생성X
			uniqueInstance = new Singleton(); //uniqueInstance가 존재하지 않기 때문에 생성, 반환
		}
		return uniqueInstance;
	}
}

싱글톤 패턴(Singleton Pattern)은 클래스 인스턴스를 하나만 생성하고, 그 인스턴스로의 전역 접근을 제공한다

  • Singleton Pattern을 적용할 때는, 클래스에서 하나뿐인 인스턴스를 관리하도록 만들어야 한다. 즉, 외부의 클래스에서 자신의 인스턴스를 추가적으로 만들지 못하도록 해야한다.
  • 또한, 외부 클래스에서 유일한 인스턴스에 접근할 수 있도록 전역 접근 지점을 제공해야한다. 언제든 이 인스턴스가 필요해지면, 클래스를 통해서 호출할 수 있어야한다.

3. Singleton Pattern과 멀티 스레딩


초콜릿 공장을 운영하고 있다고 가정해보자. 초콜릿을 제작하는 과정에서 전체적인 공정을 제어하는 인스턴스는 1개인 것이 안정적이기 때문에, 다음과 같이 제작했다.

public class ChocolateBolier{
	private static ChocolateBolier uniqueInstance;
	private ChocolateBolier(){}
	
	public static ChocolateBolier getInstance(){
		if(uniqueInstance == null){
			uniqueInstance = new ChocolateBolier();
		}
		return uniqueInstance;
	}
	
	public void fill(){
		// 초콜릿을 채움
	}
	
	public void drain(){
		// 초콜릿을 옮김
	}
	
	public void boil(){
		// 초콜릿을 중탕함
	}
}

아마도 의도한 대로 잘 작동할 것이다. 하지만, 여기서 멀티스레드를 사용한다면 오작동할 가능성이 있다.

만약 2개의 초콜릿 공정 스레드가 있다고 가정하자. 각각의 스레드들은 초콜릿을 만들기 위해서 ChocolateBolier 클래스의 getInstance() 메서드를 호출할 것이고, 이 과정에서 인스턴스가 없다면 생성, 있다면 생성하지 않을 것이다.

먼저 1번 스레드가 getInstance()메서드를 호출한다. 인스턴스가 없기 때문에, 새로운 인스턴스를 생성하려고 하는 찰나, 타임아웃으로 인해서 2번 스레드가 작동된다면, 아직 인스턴스가 생성되지 않았기 때문에, 인스턴스를 생성할 것이다. 그렇게 2번 스레드가 완료되고 다시 1번 스레드가 작동한다면, 이전에 멈춰있던 부분부터 다시 실행하여 인스턴스를 마저 만들게 되고 그렇게 2개의 인스턴스가 생성되게 된다.

이를 해결하기 위해서 getInstance()synchronized 키워드를 추가하여, 하나의 스레드가 메서드의 사용을 끝내기 전까지 다른 스레드들을 대기해야 한다.

public static synchronized ChocolateBolier getInstance(){
		if(uniqueInstance == null){
			uniqueInstance = new ChocolateBolier();
		}
		return uniqueInstance;
	}

4. 멀티스레딩 문제를 효율적으로 해결하기


  1. 속도가 그리 중요하지 않다면 그냥 둔다
  2. 인스턴스가 필요할때 생성하기보다 그냥 처음부터 만든다
  3. DCL(Double-Checked Locking)으로 동기화 부분을 줄인다.
    • 이를 사용하면, 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않을때만 동기화할 수 있다.

5. Singleton Pattern의 단점


  • 리플렉션, 직렬화, 역직렬화에서 문제가 될 수 있다.
  • loose coupling을 위배한다.
  • SOILD원칙에서 SRP를 위배한다.
profile
게임개발자(희망)의 공부일지

0개의 댓글