소프트웨어 디자인 패턴에서 싱글턴 패턴(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글턴 패턴이라고 한다.
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static final Singleton INSTANCE = new Singleton();
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
static final이라 멀티 쓰레드 환경에서도 안전
BUT static member는 객체를 사용하지 않더라도 메모리에 적재되기 때문에 공간 자원 낭비 발생
AND 예외 처리 불가!
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
// static 블록을 이용해 예외 처리
static {
try {
instance = new Singleton();
} catch (Exception e) {
throw new RuntimeException("싱글톤 객체 생성 오류");
}
}
public static Singleton getInstance() {
return instance;
}
}
예외는 처리 되지만 여전히 static으로 선언되어 있어 공간 차지!
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
// 외부에서 정적 메서드를 호출하면 그제서야 초기화 진행 (lazy)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 오직 1개의 객체만 생성
}
return instance;
}
}
고정 메모리 차지를 없앰!
BUT 쓰레드 세이프 하지 않는 치명적인 단점을 가지고 있음
class Singleton {
private static Singleton instance;
private Singleton() {}
// synchronized 메서드
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized 를 통해 하나의 쓰레드만 통과할 수 있도록 만듬!
BUT 성능이 너무 떨어진다!
class Singleton {
private static volatile Singleton instance; // volatile 키워드 적용
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
// 메서드에 동기화 거는게 아닌, Singleton 클래스 자체를 동기화 걸어버림
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton(); // 최초 초기화만 동기화 작업이 일어나서 리소스 낭비를 최소화
}
}
}
return instance; // 최초 초기화가 되면 앞으로 생성된 인스턴스만 반환
}
}
매번 synchronized 동기화를 실행하는 것이 문제라면, 최초 초기화할때만 적용하고 이미 만들어진 인스턴스를 반환할때는 그냥 반환함!
이때 인스턴스 필드에 volatile 키워드를 붙여주어서 I/O 불일치 문제를 해결함
BUT JVM에 따라서 쓰레드 세이프 하지 않는 경우가 있을수도 있음
class Singleton {
private Singleton() {}
// static 내부 클래스를 이용
// Holder로 만들어, 클래스가 메모리에 로드되지 않고 getInstance 메서드가 호출되어야 로드됨
private static class SingleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingleInstanceHolder.INSTANCE;
}
}
멀티쓰레드 환경에서도 안전하고 Lazy Loading도 가능한 완벽한 싱글톤
but 클라이언트가 임의로 싱글톤을 파괴할 수 있다.
enum SingletonEnum {
INSTANCE;
private final Client dbClient;
SingletonEnum() {
dbClient = Database.getClient();
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
public Client getClient() {
return dbClient;
}
}
public class Main {
public static void main(String[] args) {
SingletonEnum singleton = SingletonEnum.getInstance();
singleton.getClient();
}
}
Bill Pugh Solution 기법과 달리 클라이언트에서의 공격에도 안전
BUT 싱글톤 클래스를 멀티톤(일반적인 클래스)로 마이그레이션 해야할 때 처음부터 코드를 다시 짜야함
AND 클래스 상속이 필요할 때, enum 외의 클래스 상속은 불가함
다양한 방법이 각각의 단점을 보완하기 위해서 발전되어 왔고
만약 싱글톤 패턴 클래스를 만든다면
LazyHolder : 성능이 중요하다면
Enum : 직렬화, 안정성 이 중요하다면
이렇게 선택하여 사용하면 된다
정적 메서드는 클래스의 인스턴스 없이 호출이 가능한 메서드이다.
모든 메소드가 static인 클래스를 지칭,
inner static class를 뜻하기도 한다.
상태를 가지고 있지 않고 global access를 제공해야할 때 유용
static은 컴파일 할 때 static binding 으로 싱글톤보다 좀 더 빠름
클래스 자체에 static을 붙여 사용할 수 없음 (inner class일 때만 가능)
public static void printValue(final String value) {
System.out.println(value);
}
싱글턴은 단 하나의 클래스 인스턴스만 필요하고, 모든 곳에서 동일한 상태를 유지하고 싶은 경우 사용한다. 이후 클래스 확장을 통한 메서드 재정의가 필요한 경우에 사용한다. 테스트가 어려운 정적 메서드에 비해 싱글턴은 mocking 을 통한 테스트가 용이하다.
그러나 내부 상태를 변경할 필요가 없고, 각 메서드가 매개 변수에 대해서만 작동한다면 정적 메서드를 사용할 수 있다. 또한 다형성이 필요 없고 앞으로 객체 지향을 적용할 필요가 없을 때도 사용 가능하다.
Singleton(싱글톤) vs Static Method (정적 메서드)
싱글톤 패턴 vs 정적 클래스 (Singleton Pattern vs Static Class)
싱글톤과 정적클래스
싱글톤(Singleton) 패턴 - 꼼꼼하게 알아보자