싱글톤 패턴
클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공합니다.
- 클래스에서 하나뿐인 인스턴스를 관리하게 하여야 하고 다른 어떤 클래스에서도 자신의 인스턴스를 추가로 만들지 못하게 해야 하며 필요하다면 반드시 클래스 자신을 거치도록 해야 합니다.
- 어디서든 접근할 수 있도록 전역 접근 지점을 제공하고, 언제든 클래스에 요청할 수 있게 만들어 놓고, 요청이 들어오면 하나뿐인 인스턴스를 건네주도록 만들어야 한다.
싱글톤 패턴 구현
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) instance = new Singleton();
return instance;
}
}
제가 알고있는 가장 기본적인 싱글톤 패턴입니다.
생성자를 private로 선언해서 Singleton 클래스의 getInstance() 메소드를 통해서만 인스턴스를 만들 수 있습니다.
아직 인스턴스가 만들어지지 않았을 경우 private로 선언된 생성자를 사용하여 Singleton 객체를 만든 후 instance에 그 객체를 대입하는데 이러면 인스턴스가 필요한 상황이 아니라면 인스턴스를 생성하지 않습니다.
이런 방법을 게으른 인스턴스 생성이라고 부릅니다.
하지만 멀티 스레드 환경에서 두 개 이상의 스레드가 인스턴스를 호출하는 과정에서 서로 다른 두 개의 인스턴스가 만들어 지는 상황이 발생할 수 있습니다.
멀티스레딩 문제 해결하기
1. synchronized를 붙여 메서드 동기화 시키기.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) instance = new Singleton();
return instance;
}
}
getInstance()에 synchronized만 추가해주면 먼저 사용중인 스레드가 사용을 끝내기 전까지 다른 스레드는 기다리기 때문에 2개의 스레드가 동시에 두 개의 인스턴스를 만드는 상황은 일어나지 않습니다.
2. 인스턴스를 처음부터 생성하기
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
처음부터 생성하게 되면 여러 스레드가 동시에 요청해도 이미 생성된 인스턴스를 가져가므로 문제가 발생하지 않지만,
메모리를 효율적으로 관리하기 힘들다.
3. DCL(Double-Checked Locking) 사용
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) { // 처음에만 동기화
if(instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
DCL을 사용하면 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다.
처음에만 동기화 하고 나중에는 동기화하지 않아도 됩니다.
volatile는 변수의 가시성과 일관성을 보장하기 위해 사용되는데 멀티스레딩 환경에서 변수는 CPU 캐시에 저장되어 사용될 수 있는데 이 경우 변수가 변경되어도 다른 스레드에선 캐시된 값을 참조할 수 있어서 일관성 문제가 발생할 수 있습니다.
그래서 volatile를 사용하면 변수의 값을 항상 메인 메모리에서 읽고 쓸 수 있도록 보장하기 때문에 변수의 값이 변경되면 다른 스레드에서도 즉시 그 변경된 값을 볼 수 있게 되어 멀티 스레딩 환경에서 변수의 일관성을 유지하며, 혹시 모를 문제를 방지할 수 있습니다.
4. Enum 사용
public enum Singleton {
INSTANCE;
private int count;
private Singleton() {
count = 0;
}
public void addCount() {
count++;
}
public int getCount() {
return count;
}
}
public class Test {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
instance.addCount();
System.out.println(instance.getCount());
}
}
Enum은 JVM이 Enum 타입의 인스턴스를 클래스 로딩 시점에 생성하고 초기화하기 때문에 스레드에 안전하게 처리합니다.
클래스 로딩은 JVM 내부에서 동기화되기 때문에 여러 스레드가 동시에 접근하려 해도 하나의 인스턴스만 생성되고 반환합니다.
하지만 상속이 불가능하고 추가적인 메모리를 사용하기 때문에 객체가 많을 경우 사용량이 증가할 수 있습니다.
'개발 도서' 카테고리의 다른 글
디자인 패턴 - 어댑터 패턴과 퍼사드 패턴 (0) | 2023.12.12 |
---|---|
디자인 패턴 - 커맨드 패턴 (0) | 2023.12.09 |
디자인 패턴 - 팩토리 메서드 패턴, 추상 팩토리 패턴 (1) | 2023.12.02 |
디자인 패턴 - 데코레이터 패턴 (0) | 2023.11.28 |
디자인 패턴 - 옵저버 패턴 (1) | 2023.11.23 |