SW
[개발서적] 헤드퍼스트 디자인 패턴 Ch5. 싱글턴(Singleton) 패턴 본문
1. 싱글턴 패턴 vs 전역 변수
전역 변수의 단점은 애플리케이션 시작 시 객체가 생성되어 자원을 낭비할 수 있다는 것입니다.
예를 들어, 전역 변수에 자원을 많이 차지하는 객체를 대입했지만, 애플리케이션 종료까지 사용되지 않는다면 쓸모없는 자원 낭비가 됩니다. 이를 방지하기 위해 싱글턴 패턴을 사용하면 필요할 때만 객체를 생성할 수 있습니다.
2. 고전적인 싱글턴 패턴 구현법
- 클래스 내에서 자신의 유일한 인스턴스를 정적 변수로 보유
- 클래스 내부에 static 변수를 사용하여 자신의 인스턴스를 저장합니다.
- 생성자를 private으로 설정
- 외부에서 객체를 생성하지 못하도록 생성자를 private으로 선언합니다.
- 이렇게 하면 클래스 외부에서는 새로운 인스턴스를 만들 수 없습니다.
- 유일한 인스턴스에 접근할 수 있는 정적 메서드 제공
- 정적 메서드를 통해 외부에서 이 클래스의 인스턴스에 접근할 수 있도록 합니다.
- 이 메서드에서는 객체가 이미 생성되었는지 확인한 후, 생성되지 않았다면 객체를 생성하고, 생성된 인스턴스를 반환합니다.
public class Singleton {
// 유일한 인스턴스를 정적 변수로 선언
private static Singleton uniqueInstance;
// private 생성자: 외부에서 인스턴스 생성을 막음
private Singleton() {}
// 유일한 인스턴스를 반환하는 정적 메서드
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton(); // 인스턴스가 없으면 생성
}
return uniqueInstance; // 이미 생성된 인스턴스를 반환
}
}
3. 초콜릿 보일러
초콜릿 보일러는 초콜릿과 우유를 특정 조건에서만 혼합하고 가열하는 기계입니다.
하지만 두 개 이상의 인스턴스가 동시에 작동하면 시스템이 잘못된 상태에 빠질 수 있어 문제가 발생합니다.
이 문제를 해결하기 위해 싱글턴 패턴을 적용합니다.
싱글턴 패턴을 사용하면 초콜릿 보일러 클래스의 인스턴스가 하나만 생성되고, 이 인스턴스에만 접근할 수 있습니다.
이를 통해 여러 개의 보일러 인스턴스가 생성되어 발생할 수 있는 문제를 방지할 수 있습니다.
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
// 유일한 인스턴스를 저장할 static 변수
private static ChocolateBoiler uniqueInstance;
// private 생성자: 외부에서 인스턴스를 생성하지 못하게 막음
private ChocolateBoiler() {
empty = true;
boiled = false;
}
// 유일한 인스턴스를 반환하는 static 메서드
public static ChocolateBoiler getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new ChocolateBoiler();
}
return uniqueInstance;
}
public void fill() {
if (isEmpty()) {
empty = false;
boiled = false;
// 보일러에 초콜릿과 우유를 채움
}
}
public void drain() {
if (!isEmpty() && isBoiled()) {
// 보일러를 비움
empty = true;
}
}
public void boil() {
if (!isEmpty() && !isBoiled()) {
// 보일러를 가열
boiled = true;
}
}
public boolean isEmpty() {
return empty;
}
public boolean isBoiled() {
return boiled;
}
}
4. 싱글턴 패턴의 정의
싱글턴 패턴(Singleton Pattern)은 클래스 인스턴스를 하나 만들고, 그 인스턴스로의 전역 접근을 제공합니다.
5. 멀티스레딩 문제 해결하기
멀티쓰레드 환경에서 동시에 여러 쓰레드가 getInstance() 메서드를 호출할 경우, 여러 개의 인스턴스가 생성될 수 있는 위험이 있습니다.
메서드 동기화 (synchronized)
getInstance() 메서드 전체를 synchronized 키워드를 사용해 동기화하여, 한 번에 하나의 쓰레드만 메서드를 실행할 수 있게 합니다.
public static synchronized ChocolateBoiler getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new ChocolateBoiler();
}
return uniqueInstance;
}
이 방법은 안전하지만, 모든 접근이 동기화되어 성능이 저하(100배 차이)될 수 있습니다.
6. 더 효율적으로 멀티스레딩 문제 해결하기
방법 1
getInstance() 메서드의 속도가 중요하지 않다면, 동기화를 그대로 사용해도 괜찮습니다.
동기화는 구현이 간단하고 효율적일 수 있지만, 성능 저하가 발생할 수 있습니다.
만약 getInstance()가 애플리케이션에서 성능 병목이 된다면, 다른 해결 방법을 고려해야 합니다.
방법 2
싱글턴 인스턴스를 필요할 때 생성하지 말고, 처음부터 생성하는 방법입니다.
정적 초기화 부분에서 Singleton 인스턴스를 미리 생성하여, 멀티쓰레드 환경에서도 안전하게 사용할 수 있습니다.
getInstance() 메서드는 이미 생성된 인스턴스를 반환하기만 하면 되므로 간단하고 효율적입니다.
public class Singleton {
// 클래스 로딩 시점에 인스턴스를 생성
private static final Singleton uniqueInstance = new Singleton();
// private 생성자: 외부에서 인스턴스 생성 불가
private Singleton() {}
// 이미 생성된 인스턴스를 반환
public static Singleton getInstance() {
return uniqueInstance;
}
}
방법 3
DCL(Double-Checked Locking)을 사용할 수 있습니다.
getInstance() 메서드에서 인스턴스가 이미 생성되었는지 먼저 확인하고, 생성되지 않은 경우에만 동기화 블록에 들어가 인스턴스를 생성합니다.
이로써, 인스턴스가 처음 생성될 때만 동기화가 일어나고, 그 이후에는 동기화 없이 인스턴스에 접근할 수 있어 성능이 향상됩니다.
public class Singleton {
// volatile 키워드를 사용하여 인스턴스 변수 선언
private volatile static Singleton uniqueInstance;
// private 생성자: 외부에서 인스턴스 생성 불가
private Singleton() {}
// 인스턴스 생성 메서드
public static Singleton getInstance() {
if (uniqueInstance == null) { // 인스턴스가 없으면
synchronized (Singleton.class) { // 클래스에 대해 동기화
if (uniqueInstance == null) { // 다시 한 번 인스턴스가 없는지 확인
uniqueInstance = new Singleton(); // 인스턴스 생성
}
}
}
return uniqueInstance; // 이미 생성된 인스턴스를 반환
}
}
- volatile 키워드: uniqueInstance 변수를 volatile로 선언하여, 인스턴스가 생성되는 과정을 다른 쓰레드들이 정확하게 인식할 수 있도록 합니다.
- 이중 검증: getInstance() 메서드에서 인스턴스가 null인지 두 번 확인합니다. 처음에는 동기화 없이 확인하고, 두 번째는 동기화 블록 내에서 확인하여 인스턴스가 없는 경우에만 생성합니다.
방법 4
enum을 사용합니다.
enum은 자바 언어 스펙에 의해 인스턴스가 단 한 번만 생성되고, 그 인스턴스는 enum의 생애주기 동안 계속 유지됩니다.
따라서 싱글턴 인스턴스가 하나만 존재하는 것이 자동으로 보장됩니다.
public enum Singleton {
UNIQUE_INSTANCE; // enum 상수로 싱글턴 인스턴스 정의
// 기타 필요한 필드와 메서드를 정의할 수 있습니다.
public void someMethod() {
// 싱글턴 객체로 수행할 작업들
System.out.println("Doing something with the singleton instance.");
}
}
public class SingletonClient {
public static void main(String[] args) {
// Singleton 인스턴스에 접근
Singleton singleton = Singleton.UNIQUE_INSTANCE;
// 여기서 싱글턴을 사용
singleton.someMethod(); // "Doing something with the singleton instance." 출력
}
}
https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/
'개발서적 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[개발서적] 헤드퍼스트 디자인 패턴 Ch7. 어댑터 패턴과 퍼사드 패턴 (1) | 2024.08.29 |
---|---|
[개발서적] 헤드퍼스트 디자인 패턴 Ch6. 커맨드(Command) 패턴 (0) | 2024.08.15 |
[개발서적] 헤드퍼스트 디자인 패턴 Ch4. 팩토리(Factory) 패턴 (0) | 2024.07.28 |
[개발서적] 헤드퍼스트 디자인 패턴 Ch3. 데코레이터(Decorator) 패턴 (0) | 2024.07.28 |
[개발서적] 헤드퍼스트 디자인 패턴 Ch2. 옵저버(Observer) 패턴 (0) | 2024.07.14 |