1. 정의
- 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴.
2. 장점
- 하나의 인스턴스를 다른 모듈이 공유하여 사용하기 때문에 인스턴스 생성비용이 줄어든다.
- 따라서 인스턴스생성에 많은 비용이 드는 I/O 바운드 작업에 많이 사용한다. (네트워크, DB 연결, File system 등)
3. 단점
- 의존성이 높아지는 문제가 있다.
- TDD를 방해하는 문제가 있다.
- 단위 테스트 시에는 각 테스트가 독립적이어야 하는데 싱글톤 패턴으로 만든 인스턴스를 공유하기 때문에 각 테스트마다 독립적인 인스턴스를 만들기 어렵다.
4. 싱글톤 패턴을 구현하는 방법
- 단순한 메서드 호출
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance();
}
}
- 싱글톤이 없으면 새로 만들고 있다면 기존의 인스턴스를 반환한다.
- 하지만 멀티스레드 환경에서는 싱글톤 인스턴스를 2개 이상 만들 수 있기 때문에 제대로 된 방법이 아니다.
- 이럴 때는 synchronized 키워드를 사용해서 메서드를 호출하도록 하면 인스턴스 반환 전까지 격리되기 때문에 어떤 스레드가 접근한 상태일 때 다른 쓰레드가 영향을 줄 수 없도록 수정할 수 있다.
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance();
}
- 하지만 sync를 걸면 계속 락이 걸려있는 상태가 되기 때문에 성능 저하가 발생할 수 있다.
정적 멤버
- 정적 멤버나 블록은 런타임이 아니라 최초에 JVM이 모든 클래스를 로드할 때 미리 인스턴스를 생성하는 특징을 이용하는 방법이다.
- 클래스 로딩과 동시에 싱글톤 인스턴스를 만들게 된다. 따라서 모듈이 싱글톤 인스턴스를 호출할 때 클래스 로드시 미리 만들어진 인스턴스를 반환하게 된다.
- 하지만 이 방식은 싱글톤 인스턴스가 필요없는 경우에도 무조건 생성하기 때문에 자원이 낭비된다는 단점이 있다.
public class Singleton {
private final static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
정적 블록
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
- 정적 멤버 대신 정적 블록을 사용하는 방법도 있다.
정적 멤버와 Lazy Holder(중첩 클래스)
- SingletonInstanceHolder라는 내부클래스를 하나 더 만들어서 Singleton 클래스가 최초에 로딩되더라도 함께 초기화 되지 않고 getInstance()를 호출했을 때 singletonInstanceHolder 클래스가 로딩되어 인스턴스를 생성하도록 한다.
- 아래 예시처럼 new Singleton()은 할 수 없고 Singleton.getInstance()를 이용해야 한다.
class Singleton {
private Singleton() {} // private 생성자로 클래스 외부에서 인스턴스 생성을 방지
private static class singleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return singleInstanceHolder.INSTANCE;
}
}
public class Main {
public static void main(String[] args) {
Singleton a1 = new Singleton(); // 해당 인스턴스 생성을 못함.
Singleton a2 = Singleton.getInstance();
}
}
이중 확인 잠금
- DCL(Double Checked Locking)을 사용할 수도 있다.
- 인스턴스 생성 여부 점검을 싱글톤 패턴 잠금 전에 한 번, 객체 생성 전에 한 번.
- 총 2번 체크하면 인스턴스가 존재하지 않을 때만 잠글 수 있게 된다.
- volatile 키워드를 사용하는 이유.
- 자바에서는 스레드 별로 변수를 캐시 메모리에서 가져온다.
- 따라서 스레드 별로 변수값 불일치가 발생할 수 있기 때문에 volatile을 사용하여 변수값을 메인 메모리를 기반으로 저장하고 읽어오도록 하는 것이다.
public class Singleton {
private volatile Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
enum 인스턴스
- enum 인스턴스는 기본적으로 스레드세이프하기 때문에 이 특징을 이용한다.
- enum은 클래스처럼 메서드, 생성자를 모두 가질 수 있다.
- enum은 고정된 상수들의 집합으로 런타임이 아닌 컴파일타임에 모든 값을 알고 있어야 한다. 이 때문에 생성자가 private으로 제한된다.
- 스레드 세이프 특성 떄문에 코드가 간단해진다. 직렬화도 JVM이 enum의 직렬화를 보장하기 때문에 아무런 문제가 없다.
private enum EasySingleton {
INSTANCE;
public String method() {
return "singletonMethod";
}
}
public static void main(String[] args) {
EasySingleton es = es.INSTANCE;
System.out.println(es.method());
}
출처
CS 지식의 정석 | 디자인패턴 네트워크 운영체제 데이터베이스 자료구조 - 큰돌