일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Machine Learning
- NumPy
- 파이썬
- 자바
- 넘파이
- 데이터 마이닝
- 디자인 패턴
- cpp
- c++
- java
- ack
- python
- 머신러닝
- 데이터 분석
- OOP
- 코딩테스트실력진단
- 넘파이 기초
- 합성곱 신경망
- 네트워크 기초
- numpy 기초
- 차원축소
- 코테
- lambda
- 기계학습
- Design Pattern
- 코딩테스트
- 코드트리
- 클러스터링
- cpp class
- 넘파이 배열
- Today
- Total
준비하는 대학생
[Design Pattern] Singleton Pattern(싱글턴 패턴) 본문
Singleton 패턴이란?
Singleton 패턴은 객체 지향 프로그래밍에서 주로 사용되는 디자인 패턴 중 하나입니다. Singleton 패턴은 클래스의 특정 인스턴스가 한 개만 만들어지도록 보장하고, 그 인스턴스에 쉽게 접근할 수 있는 글로벌한 접근점을 제공합니다. 이를 통해 다른 객체들이 데이터를 공유할 수 있게 하며, 자원의 효율적인 관리를 할 수 있습니다.
고전적인 Singleton 패턴
싱글턴 패턴은 특정 클래스의 인스턴스가 하나만 생성되도록 보장하고, 이 인스턴스에 전역적으로 접근할 수 있게 하는 디자인 패턴입니다. 이를 사용하면 여러 객체들이 데이터를 공유할 수 있게 만들 수 있습니다.
Java로 Singleton 패턴을 구현하는 고전적인 방법은 다음과 같습니다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
고전적인 Singleton 패턴의 문제점
위의 구현은 심플하지만 멀티스레드 환경에서는 제대로 동작하지 않을 수 있습니다. 두 개의 스레드가 동시에 getInstance() 메서드에 진입하여 uniqueInstance == null 확인 후, 둘 다 새로운 인스턴스를 생성하게 될 수 있기 때문입니다.
이 문제를 해결하기 위한 한 가지 방법은 getInstance() 메서드를 동기화하는 것입니다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
하지만 이렇게 동기화를 하게 되면 성능이 저하되는 단점이 있습니다.
문제 해결을 위한 두 가지 방법
이런 문제를 해결하기 위해 'eager initialization' 방식과 'double-checked locking' 방식을 사용할 수 있습니다.
1) Eager initialization 방식 : 클래스가 로딩될 때 인스턴스를 미리 생성해 두는 것
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
2) Double-checked locking 방식: getInstance() 메서드 내에서 필요한 경우(처음)에만 동기화하는 방식
public class Singleton {
private static volatile Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
*volatile 키워드
Java에서 volatile 키워드는 멀티스레딩 환경에서 변수의 읽기와 쓰기를 원자적(atomic)으로 수행하도록 보장합니다. 이는 변수가 CPU 캐시가 아닌 메인 메모리에서 읽고 쓰이게 하여, 여러 스레드가 해당 변수의 값을 일관성 있게 볼 수 있도록 합니다.
volatile 키워드가 붙은 변수에 대한 모든 읽기와 쓰기 작업은 메모리 배리어(memory barrier)를 통해 수행되므로, 쓰기 작업이 완료되기 전에 읽기 작업이 일어나는 것을 방지합니다.
Singleton 패턴에서 'double-checked locking'을 사용할 때, uniqueInstance 변수에 volatile 키워드를 사용하여 인스턴스 생성 과정 중에 다른 스레드가 부분적으로 생성된 인스턴스를 보는 것을 방지합니다.
메모리 가시성
: Java에서 여러 스레드가 동작하면, 각 스레드는 자신만의 작업 메모리를 가지고, 이 메모리에서 변수를 읽고 쓰게 됩니다. 이 때, 한 스레드에서 변경된 변수의 값이 다른 스레드에게 언제 어떻게 보이는지를 결정하는 것이 메모리 가시성입니다. volatile 키워드를 사용하면, 변수의 변경이 즉시 메인 메모리에 반영되므로, 다른 스레드에서도 해당 변경을 즉시 볼 수 있습니다
*synchronized 키워드
Java의 synchronized 키워드는 여러 스레드가 동시에 접근할 수 있는 공유 자원에 대한 동시 접근을 제어하는 역할을 합니다. 이 키워드가 붙은 메소드 또는 블록은 한 번에 하나의 스레드만 접근할 수 있게 하여, 공유 데이터의 일관성을 유지하고 경쟁 상태(race condition)를 방지합니다.
Singleton 패턴에서 getInstance() 메서드를 synchronized로 선언하면, 한 번에 하나의 스레드만이 해당 메서드를 실행할 수 있습니다. 하지만 이 방식은 성능 저하를 일으킬 수 있습니다. 따라서 'double-checked locking'에서는 getInstance() 메서드 전체를 동기화하는 대신, 인스턴스가 아직 생성되지 않았을 때만 synchronized 블록을 실행하여 성능 저하를 최소화합니다.
동기화(synchronization)
: 여러 스레드가 동시에 공유 메모리에 접근하여 데이터의 일관성을 해칠 수 있는 상황을 방지하는 것이 동기화입니다. synchronized 키워드를 사용하면, 한 번에 하나의 스레드만이 synchronized로 표시된 코드 블록 또는 메서드에 접근할 수 있게 됩니다.
이 두 방법은 각각 성능 문제와 lazy initialization을 유지하는 장점을 가지고 있습니다.
Enum을 이용한 Singleton 패턴
그럼에도 불구하고 Singleton 패턴은 아직도 몇 가지 문제점이 있습니다. 가장 큰 문제는 바로 'serialization'입니다. 객체가 직렬화되었다가 다시 역직렬화될 때, 새로운 인스턴스가 생성될 수 있습니다. 이 문제를 해결하기 위해선 `readResolve()` 메서드를 구현해야 합니다. 하지만 이것도 코드가 복잡해지고, 실수로 버그를 만들 가능성이 높아집니다. 하지만 Java에서는 Enum을 이용해 이런 문제를 해결할 수 있습니다. Enum은 Java가 자동으로 직렬화를 관리하며, 인스턴스가 하나만 생성되도록 보장합니다.
public enum Singleton {
INSTANCE;
public void doSomething() {
// do something
}
}
이렇게 Enum을 이용하면 직렬화 문제가 해결될 뿐만 아니라, 리플렉션(reflection)을 통한 인스턴스 생성도 막을 수 있습니다. 그리고 코드도 훨씬 간결해집니다.
결론
싱글턴 패턴은 자원을 효율적으로 관리할 수 있도록 도와주지만, 동시성 문제나 직렬화 문제 등 여러 가지 주의해야 할 점이 있습니다. 이런 문제점들을 이해하고, 적절한 해결책을 선택하는 것이 중요합니다.
Java에서는 Enum을 이용한 싱글턴 패턴 구현이 가장 안전하고 간결하다고 권장되고 있습니다. 그러나 어떤 방식을 선택하든 그 방식의 장단점을 이해하고 있는 것이 중요합니다.
'Programming > Design pattern' 카테고리의 다른 글
[Design Pattern] Adapter Pattern(어댑터 패턴) (0) | 2023.06.02 |
---|---|
[Design Pattern] Command Pattern(커맨드 패턴) (0) | 2023.06.02 |
[Design Pattern] 데코레이터 패턴(Decorator Pattern) (0) | 2023.04.10 |
[Design Pattern] Observer Pattern(옵저버 패턴) (0) | 2023.03.29 |
[Design Pattern] Strategy Pattern(전략 패턴) (0) | 2023.03.22 |