싱글턴(Singleton) 패턴은 하나의 클래스에서 단 하나의 인스턴스만 생성되도록 보장하는 디자인 패턴이다.
이 패턴을 사용하면 프로그램 전역에서 동일한 객체를 공유할 수 있다.
필요할 때 인스턴스를 생성하는 방식이다.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
문제점: 멀티스레드 환경에서는 여러 개의 인스턴스가 생성될 가능성이 있다.
멀티스레드 환경에서도 안전하게 인스턴스를 생성하기 위해 synchronized 키워드를 사용한다.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
단점: synchronized를 사용할 경우 성능 저하가 발생할 수 있다.
동기화로 인한 성능 저하를 줄이기 위해 두 번 검사하는 방식이다.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
특징
volatile 키워드를 사용하여 인스턴스가 완전히 초기화되기 전에 다른 스레드에서 접근하는 문제를 방지한다. 클래스가 로드될 때 미리 인스턴스를 생성하는 방식이다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
특징
클래스가 로드될 때 인스턴스를 생성하지 않고, 필요할 때 생성할 수 있도록 하는 방식이다.
public class Singleton {
private Singleton() {}
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
장점
가장 안전한 방법으로, Java의 Enum을 활용하여 싱글턴을 구현하는 방식이다.
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("싱글턴 객체 사용 중...");
}
}
특징
Enum은 Java에서 자체적으로 싱글턴을 보장하므로 멀티스레드 환경에서도 안전하다. Java의 리플렉션(Reflection)을 사용하면 private 생성자라도 강제로 호출하여 새로운 인스턴스를 생성할 수 있다.
import java.lang.reflect.Constructor;
public class SingletonBreak {
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();
System.out.println(instance1 == instance2); // false → 싱글턴 깨짐
}
}
이 문제를 방지하려면 생성자에서 이미 인스턴스가 존재할 경우 예외를 던지는 방식을 사용해야 한다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
if (INSTANCE != null) {
throw new RuntimeException("싱글턴 인스턴스는 이미 존재합니다!");
}
}
public static Singleton getInstance() {
return INSTANCE;
}
}
이렇게 하면 리플렉션을 사용해 새로운 인스턴스를 만들려고 하면 예외가 발생한다.
싱글턴 객체를 직렬화하면 새로운 객체가 생성될 수 있다. 이를 방지하려면 readResolve() 메서드를 구현해야 한다.
protected Object readResolve() {
return INSTANCE;
}
Spring Framework
@Component, @Service, @Repository 같은 애노테이션을 사용하면 자동으로 싱글턴이 적용된다. 데이터베이스 커넥션 풀
캐싱 시스템
로그 관리
싱글턴 패턴은 특정 객체가 단 하나만 존재해야 하는 경우 유용하게 사용될 수 있다.
하지만 잘못 사용하면 테스트가 어려워지고, 의존성 주입(DI)과 충돌할 수도 있다.