공부/소프트웨어공학

[디자인패턴] 싱글톤(Singleton) 패턴이란?

돌멩이수프 2024. 11. 8. 15:33
728x90

싱글톤 패턴은 Creational Design Pattern이다.


무엇인가?

- 단 하나의 클래스 객체(instance)가 생기도록하는 패턴이다. 어디서나 접근 가능하다.

왜 사용하나?

- 어디에서 접근 가능한 객체가 필요한 경우가 생기기 때문이다.

최초로 객체를 생성하고 난 뒤에 추가로 객체를 생성할 필요가 없으니 메모리, 속도 면에서 이점이 있다.

 

public static Logger getInstance() {
	if (instance == null)
		instance = new Logger();
	return instance;    
}

이렇게 ​instance가 null이라면 새로운 instance를 생성하고, instance가 이미 있다면 기존 instance를 사용하는 방식이다. 

싱글 스레드에서는 아무 문제 없는 코드이나 멀티 스레드로 넘어가면 상황이 달라진다. instance가 한 번도 생성되지 않은 상황에서 각 스레드는 모두 instance가 null이라고 인식할 것이다. 모든 스레드가 각자의 instance를 갖게 된다... 이는 Sigleton 패턴이 아니다.

 

이를 해결하기 위해 Java에서는 Synchronized로 동기화를 진행한다.

public synchronized static Logger getInstance() {
	if (instance == null) 
		instance = new Logger();
	return instance;
}

이때 getInstance 메서드를 static으로 설정해 프로그림이 실행될 때부터 끝날 때까지 메모리를 할당한 채로 남아있도록 한다. synchronized를 사용하면 메서드의 시작과 끝을 전부 동기화로 잡고 다른 작업을 할 수 없기 때문에 효율이 떨어진다. DCL(Double Checked Locking)을 사용하여 필요한 부분만 동기화를 진행해 효율을 높이도록 하자.

public static Logger getInstance() {
	if (instance == null) {
		synchronized(Logger.class) {
			if (instance == null)
				instance = new Logger();
		}
	}
	return instance;
}

첫 번째 if문은 instance가 null인지 아닌지를 확인하여 instance가 null인 경우에만 sychronized를 사용하도록 한다. 코드 효율 향상을 위한 if문이다.

두 번째 if문은 정말 instance가 null인지 확인하여 (첫 번째 if문 이후로 다른 스레드가 진입해 instance가 존재하는 스레드가 새로운 instance를 생성하지 않도록 하기 위해) 하나의 instance만을 생성한다.

 

DCL은 잘 작동한다. 그러나... 또 다른 문제가 있다...

프로그램의 진행 순서를 가정해보자.

  1. Logger 인스턴스를 위해 메모리 할당
  2. 생성자를 통한 초기화
  3. 할당된 메모리를 instance 변수에 할당

이런 순서가 변하지 않고 진행되면 좋겠지만 컴파일러는 어셈블리 명령어 reorder를 진행한다. 즉, 자신이 생각하는 올바른 순서대로 프로그램 순서를 변경한다.

  1. Logger 인스턴스를 위해 메모리 할당
  2. 할당된 메모리를 instance 변수에 할당
  3. 생성자를 통한 초기화

만약 이런식으로 instance를 할당한 후에 생성자를 초기화한다면 초기화되지 않은 isntance가 반환되어 스레드가 사용할 가능성이 있다. null이어야 할 instance가 null이 아닌 것으로 사용될 가능성이 있다...

 

✅ 그래서 사용하는 것이 volatile 변수다.

이때 volatile 키워드를 사용할 경우 visibility를 보장할 수 있게 된다. (flag 값을 코어의 chache에서 읽어오는 게 아니라 Main memory에서 읽어옴)

 

👉DCL(Double Checked Locking)에서 volatile 변수를 사용하는 이유👈

  1. 재정렬 방지
  2. 가시성 보장

 

Signleton 생성 방식에는 2가지가 있다. Eager InitializationDemand(Lazy) Holder 방식이다.

Eager Initialization은 static 변수를 사용하여 class가 생성될 때 함께 instance를 생성하고 후에는 getInstance등의 메서드를 사용하여 미리 생성된 instance를 불러오는 방식이다.

Demand Holder 방식은 instance를 부르기 전까지는 instance가 없다가 getInstance 등을 호출하면 그때 instance를 생성하는 방식이다.

public class Something {
	private Something() {	}
	private static class LazyHolder {
		public static final Something INSTANCE = new Something()
	}
	public static Something getInstace() {
		return LazyHolder.INSTANCE;
	}
}

 

 

 

 

참고자료

https://csharpindepth.com/articles/singleton

 

Implementing the Singleton Pattern in C#

Implementing the Singleton Pattern in C# Table of contents (for linking purposes...) The singleton pattern is one of the best-known patterns in software engineering. Essentially, a singleton is a class which only allows a single instance of itself to be cr

csharpindepth.com

https://refactoring.guru/ko/design-patterns/singleton

 

싱글턴 패턴

/ 디자인 패턴들 / 생성 패턴 싱글턴 패턴 다음 이름으로도 불립니다: Singleton 의도 싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근​(액세스) 지점을 제공하

refactoring.guru

 

728x90