
싱글턴 패턴 - 클래스 인스턴스를 하나만 만들고 그 인스턴스로의 전역 접근을 제공하는 방법
단순하게 어떤 클래스에 대한 인스턴스가 단 하나 유일하게 존재하는 것이다.
어떤 이점이 있을까?
우선 단 하나의 인스턴스만 생성되면 메모리 측면에서 비용을 아낄 수 있을 것이다.
두번째는 전역 접근이 가능하기 때문에 다른 클래스로부터 접근하기 쉽다.
어떻게 생겼는지 확인해보자
public class Singleton {
private static Singleton instance;
private Singleton(){} // 생성자를 private으로 한다.
public static Singleton getInstance() {
if(instance == null){
instance = = new Singleton();
}
return instance;
}
public void fuc() {
// ... 여러 기능들 ...
}
}
위는 고전적인 싱글턴 패턴 구현 방법이다.
우선 자기자신 인스턴스를 static으로 가지고 있으며
생성자를 private하게 하여 외부에서 생성하지 못하도록 한다.
getInstance()로 해당 인스턴스를 불러올 수 있다.
위는 고전적인 구현 방법이라고 했는데 어떤 문제가 있을까?
바로 동시성 문제가 있다.
두개의 스레드에서 getInstance()를 동시에 접근하면 무슨일이 발생할까?
if(instance == null){
if(instance == null){
instance = new Singleton();
instance = new Singleton();
왼쪽과 오른쪽으로 순차적으로 두 스레드가 진행된다면 2개의 인스턴스가 생길 수 있는 문제가 발생한다.
싱글턴의 치명적인 오류이다.
3가지 방법으로 위 문제를 개선할 수 있다.
1) synchronized 사용
public class Singleton {
private static Singleton instance;
private Singleton(){} // 생성자를 private으로 한다.
public static synchronized Singleton getInstance() { --- synchronized를 사용하면 됨
if(instance == null){
instance = = new Singleton();
}
return instance;
}
public void fuc() {
// ... 여러 기능들 ...
}
}
synchronized를 사용하여 동시에 스레드가 접근하지 못하도록 메서드를 보호할 수 있다.
하지만 getInstance()를 할때마다 동시 접근이 안되기 때문에 100배정도 속도 저하가 있을 수 있다고 한다.
속도에 크게 상관하지 않는다면 괜찮은 방법이다.
2) 처음부터 인스턴스 생성
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){} // 생성자를 private으로 한다.
public static Singleton getInstance() {
return instance;
}
public void fuc() {
// ... 여러 기능들 ...
}
}
인스턴스를 바로 생성하는 방법이다. 1)번 방법에 있던 속도저하문제는 해결할 수 있다.
굳이 문제?라고 뽑자면 바로 인스턴스를 생성하기 때문에 사용하기 전부터 메모리를 잡아먹고 있다.
3) DCL (double-checked locking)
public class Singleton {
private volatile static Singleton instance;
private Singleton(){} // 생성자를 private으로 한다.
public static synchronized Singleton getInstance() {
if(instance == null){ --- 인스턴스 있는지 확인
synchronized (Singleton.class){ --- 동기화된 블록으로 진입
if(instance == null){ --- 다시 인스턴스 있는지 확인(여기부터는 동기화니까 자기만 확인함)
instance = = new Singleton(); --- 없다면 생성
}
}
}
return instance;
}
public void fuc() {
// ... 여러 기능들 ...
}
}
인스턴스가 null 일때만 동기화를 적용해서 1, 2 번 문제를 모두 해결한 방법입니다.
### 여기서 instance에서 volatile이 붙는데 왜 붙었을까? 생각해봤는데 synchronized만 하면 동기화문제가 해결되는데 instance가 2개 생성되는 문제가 발생할 수도 있다고 한다(거의 불가능하지만)
아까 위에서 본 main memory - cache - cpu 구조에서 A 스레드, B 스레드가 있을때 synchronized되어있으니 A가 먼저 진입해서 instance 생성을 했다(main memory에서 null이니까) 근데 이제 main memory에서 B의 cache에 적용하려고하는데(null값을 새로만든 instance값) 그 전에 B 스레드가 실행해서 cache(null)값을 보게 된다면? 또 instance를 생성하게 될 수도 있을 것 같다... ###
마지막으로 enum을 사용하여 정말 간단하게 싱글턴 패턴을 만들 수 있습니다!
싱글턴 패턴에는 어떤 단점이 있을까요?
전역접근이 가능하기 때문에 여러 클래스가 의존하고 있습니다.
전역으로 공유하기 때문에 격리된 환경에서 테스트하기 어렵습니다.
참고
'리팩토링 > 디자인패턴' 카테고리의 다른 글
| [디자인패턴] 퍼사드 패턴 (1) | 2022.11.26 |
|---|---|
| [디자인패턴] 어댑터 패턴 (1) | 2022.11.14 |
| [디자인패턴] 팩토리 메서드 패턴, 추상 팩토리 패턴 (1) | 2022.10.30 |
| [디자인패턴] 옵저버 패턴 (4) | 2022.10.14 |
| [디자인패턴] 전략 패턴 (6) | 2022.10.10 |