싱글턴 패턴에 대해서 알아보도록 하겠다.
싱글턴 패턴
객체지향 원칙
바뀌는 부분은 캡슐화한다.
상속보다는 구성을 활용한다.
구현보다는 인터페이스에 맞춰서 프로그래밍한다.
상호작용하는 객체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.
클래스는 확장에는 열려있어야 하지만 변경에는 닫혀 있어야 한다.
추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.
싱글턴 패턴(Singleton Pattern)
클래스 인스턴스를 하나만 말도고 그 인스턴스의 전역 접근을 제공한다.
싱글턴 패턴
객체 중 하나만 있어도 잘 돌아가거나, 하나만 있어야 하는 경우가 있다. 스레드 풀, 캐시, 사용자 설정, 레지스트리 설정 처리하는 객체, 로그 기록 객체, 디바이스 드라이버 등이 그 예이다. 인스턴스가 2개 이상이면 프로그램이 이상하게 돌아간다든가, 자원을 불필요하게 사용한다든가, 결과에 일관성이 없어지는 문제를 유발한다. 이 문제를 해결하기 위해 싱글턴 패턴이 만들어졌다.
싱글턴 패턴을 이해하기 위해 다음 내용을 알아야 한다.
1개의 객체를 만들기 위해 new MyObject();와 같이 new 연산자를 사용해야 하며 생성자를 호출한다. 만약에 생성자가 다음과 같이 private으로 설정되어 있다면 어떻게 해야 될까?
public MyClass {
private MyClass() {};
}
생성자가 private으로 설정되어 있으면 다른 외부 클래스에서 사용하지 못하고 MyClass에 있는 코드(메서드)에서만 호출할 수 있다. 즉 다른 클래스에서 new MyClass()를 사용하지 못한다.
MyClass안에 정적 메서드를 활용해 new MyClass()를 사용할 수 있으며 다른 클래스에서 이 정적 메서드를 활용할 수 있다. 정적 메서드 이름을 getInstance()라고 선언하겠다.
public MyClass{
private MyClass() {}
public static MyClass getInstance() {
return new MyClass();
}
}
다른 외부 클래스에서 MyClass.getInstance()를 통해 MyClass 인스턴스를 생성하고 호출할 수 있다.
위 개념을 활용하여 싱글턴 패턴을 구현해 보도록 하겠다. 코드는 다음과 같다.
public class Singleton {
private static Singleton singletonInstance;
// 기타 인스턴스 변수
private Singleton() {};
public static Singleton getInstance() {
if(singletonInstance == null) {
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
하나뿐인 인스턴스를 저장하는 정적 변수인 singletonInstance를 선언하고 getInstance() 메서드에서 클래스의 인스턴스를 만들어서 리턴한다. singletonInstance가 처음부터 인스턴스를 할당받는 것이 아닌 getInstance()가 처음 호출되었을 때 인스턴스가 저장된다. 이런 방법을 '게으른 인스턴스 생성'이라고 부른다.
동기화 문제 해결
멀티 스레드 환경에서 getInstance()가 크리티컬 섹션이 되고, 따라서 동기화 문제가 발생할 수 있다. 이를 해결하기 위해 Java에서 제공하는 synchronized를 통해 문제를 해결할 수 있다. 코드는 다음과 같다.
public class Singleton {
private static Singleton singletonInstance;
// 기타 인스턴스 변수
private Singleton() {};
public static synchronized Singleton getInstance() {
if(singletonInstance == null) {
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
synchronized를 사용했을 때 문제점은 동기화 문제를 해결하지만 성능이 저하된다는 점이다. getInstance()의 속도가 중요하지 않다면 그냥 두면 되지만 성능이 중요하다면 다음 2가지 방법을 활용하면 된다.
singletonInstance에 Singleton 인스턴스를 바로 생성하는 방법이다. 클래스가 로딩될 때 JVM에서 singletonInstance에 인스턴스를 생성해준다.
public class Singleton {
private static Singleton singletonInstance = new Singleton();
// 기타 인스턴스 변수
private Singleton() {};
public static Singleton getInstance() {
return singletonInstance;
}
}
다음은 DCL(Double-Checked Locking)을 써서 동기화되는 부분을 줄이는 방법이다. DCL로 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때 동기화하면 된다. volatile 키워드를 사용하면 멀티스레딩을 쓰더라도 singletonInstance 변수가 Singleton 인스턴스로 초기화되는 과정이 올바르게 진행된다.
코드는 다음과 같다.
public class Singleton {
private volatile static Singleton singletonInstance;
// 기타 인스턴스 변수
private Singleton() {};
public static Singleton getInstance() {
if(singletonInstance == null) {
synchronized (Singleton.class) {
if(singletonInstance == null) {
singletonInstance = new Singleton();
}
}
}
return singletonInstance;
}
}
DCL과 volatile에 관한 내용은 추후에 설명하도록 하겠다.
ENUM
싱글턴 패턴을 활용하더라도 여러 문제가 발생할 수 있다. 클래스 로더가 2개 이상인 경우 다른 싱글턴 인스턴스를 가질 수 있으며, 리플랙션, 직렬화, 역직렬화 시 싱글턴에서 문제가 될 수 있다.
이 문제를 생각보다 간단하게 해결 가능하다. 그냥 enum을 쓰면 된다. enum을 사용하면 동기화 문제, 클래스 로딩 문제, 리플랙션, 직렬화, 역직렬화 문제 등을 해결할 수 있다.
public enum Singleton {
UNIQUE_INSTANCE;
}
public class Main {
public static void main() {
Singleton singleton = Singleton.UNIQUE_INSTANCE;
}
}
'디자인패턴' 카테고리의 다른 글
[헤드퍼스트 디자인패턴] 템플릿메서드 패턴 (0) | 2024.08.20 |
---|---|
[헤드퍼스트 디자인패턴] 커맨드 패턴 (0) | 2024.08.17 |
[헤드퍼스트 디자인패턴] 팩토리 패턴 (0) | 2024.08.12 |
[헤드퍼스트 디자인패턴] 데코레이터 패턴 (0) | 2024.08.06 |
[헤드퍼스트 디자인패턴] 옵저버 패턴 (0) | 2024.07.30 |