싱글톤 패턴은 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은 잘 작동한다. 그러나... 또 다른 문제가 있다...
프로그램의 진행 순서를 가정해보자.
- Logger 인스턴스를 위해 메모리 할당
- 생성자를 통한 초기화
- 할당된 메모리를 instance 변수에 할당
이런 순서가 변하지 않고 진행되면 좋겠지만 컴파일러는 어셈블리 명령어 reorder를 진행한다. 즉, 자신이 생각하는 올바른 순서대로 프로그램 순서를 변경한다.
- Logger 인스턴스를 위해 메모리 할당
- 할당된 메모리를 instance 변수에 할당
- 생성자를 통한 초기화
만약 이런식으로 instance를 할당한 후에 생성자를 초기화한다면 초기화되지 않은 isntance가 반환되어 스레드가 사용할 가능성이 있다. null이어야 할 instance가 null이 아닌 것으로 사용될 가능성이 있다...
✅ 그래서 사용하는 것이 volatile 변수다.
이때 volatile 키워드를 사용할 경우 visibility를 보장할 수 있게 된다. (flag 값을 코어의 chache에서 읽어오는 게 아니라 Main memory에서 읽어옴)
👉DCL(Double Checked Locking)에서 volatile 변수를 사용하는 이유👈
- 재정렬 방지
- 가시성 보장
Signleton 생성 방식에는 2가지가 있다. Eager Initialization과 Demand(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
'공부 > 소프트웨어공학' 카테고리의 다른 글
[디자인패턴] 커맨드(Command) 패턴이란? (0) | 2024.11.08 |
---|---|
[디자인패턴] 스트래티지(Strategy) 패턴이란? (0) | 2024.11.08 |
[소프트웨어공학] SOLID 원칙에 대하여 (0) | 2024.05.13 |
[소프트웨어공학] Coupling(결합도)란? (0) | 2024.04.19 |
[소프트웨어공학] Cohesion(응집도)란? (0) | 2024.04.19 |