생성 패턴(Creational Pattern)
싱글톤 패턴(Singleton Pattern)은 객체 생성 패턴 중 하나로, 오직 하나의 인스턴스만을 생성하고 이에 대한 전역적인 접근점을 제공하는 패턴입니다.
싱글톤 패턴을 사용하면 어디서든 동일한 인스턴스를 참조할 수 있으므로, 시스템 내에서 공통적으로 사용되는 자원에 대한 중복 생성 및 관리를 효과적으로 방지할 수 있습니다.
싱글톤 패턴의 주요 특징은 다음과 같습니다.
- 클래스 내부에서 스스로 인스턴스를 생성하고 유일한 인스턴스에 대한 접근을 제공합니다.
- 생성된 인스턴스는 전역적으로 접근 가능한 변수에 저장되며, 모든 클라이언트는 이 변수를 통해 인스턴스에 접근합니다.
- 다중 스레드 환경에서 안전한 인스턴스 생성 방법이 필요합니다.
싱글톤 패턴은 자원의 효율성을 높이고 중복 생성을 방지할 수 있는 장점이 있지만, 과도한 사용은 의존성이 복잡해지고 유지보수가 어려워질 수 있다는 단점이 있습니다. 따라서 적절한 상황에서 사용해야 합니다.
Singleton 패턴 예제
1. Eager Initialization(이른 초기화) 방식
이 방식은 클래스 로딩 시점에 Singleton 클래스의 인스턴스를 생성하여 미리 초기화하는 방식입니다. 따라서 객체 생성에 대한 비용이 매우 적습니다. 이 방식은 초기화 과정에서 발생할 수 있는 스레드 안정성 문제를 걱정하지 않아도 되는 장점이 있습니다.
public class Singleton {
// 클래스 로딩 시점에서 인스턴스를 생성합니다.
private static final Singleton INSTANCE = new Singleton();
// 생성자를 private으로 선언하여 외부에서 객체 생성을 막습니다.
private Singleton() {}
// 외부에서 인스턴스에 접근할 수 있도록 정적 메서드를 제공합니다.
public static Singleton getInstance() {
return INSTANCE;
}
}
2. Lazy Initialization(늦은 초기화) 방식
이 방식은 실제로 객체가 필요할 때 인스턴스를 생성하는 방식입니다. 그러나 멀티스레드 환경에서 동시에 getInstance() 메서드를 호출하면 여러개의 인스턴스가 생성될 가능성이 있으므로, synchronized 키워드를 이용하여 동기화하여 해결합니다. 하지만 synchronized 키워드를 사용하면 성능 저하가 발생할 수 있습니다.
public class Singleton {
private static Singleton instance;
private Singleton() {}
// synchronized 키워드를 이용하여 동기화합니다.
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. Double-Checked Locking(이중 검사 락) 방식
Lazy Initialization 방식에서 성능 문제를 해결하기 위한 방식입니다. 첫 번째 null 체크에서는 객체가 생성된 경우에는 synchronized 블록에 진입하지 않습니다. 이로 인해 객체가 생성되는 비용이 많이 들지 않습니다. 두 번째 null 체크에서는 synchronized 블록에서 객체를 생성합니다. 그리고 volatile 키워드를 사용하여 메모리 가시성 문제를 방지합니다.
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
위 예제에서는 instance 변수가 생성되지 않은 경우에만 동기화 블록을 실행합니다. 이렇게 함으로써, 불필요한 동기화를 피하고 인스턴스가 여러 번 생성되는 것을 방지할 수 있습니다.
4. Enum Singleton(열거형 싱글톤) 방식
먼저 직렬화 문제에 대해 소개해드리겠습니다.
import java.io.*;
public class Singleton implements Serializable {
private static Singleton instance;
private Singleton() {
// 생성자를 private으로 선언하여 외부에서 인스턴스를 생성하지 못하도록 합니다.
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(singleton);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton singleton2 = (Singleton) ois.readObject();
ois.close();
System.out.println(singleton == singleton2); // false
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
위 코드에서는 싱글톤 객체를 생성하고, ObjectOutputStream을 사용하여 객체를 파일로 직렬화하고, ObjectInputStream을 사용하여 파일로부터 객체를 역직렬화합니다. 그리고나서, 두 객체를 비교하여 같은지 여부를 출력합니다.
실행결과는 false가 나타납니다. 이는 객체를 역직렬화하여 새로운 인스턴스를 생성했기 때문입니다. 즉, 객체를 직렬화하고 역직렬화할 때마다 새로운 인스턴스가 생성되는 문제가 발생하는 것을 알 수 있습니다.
하지만 Enum 타입은 생성자가 private이고, 인스턴스를 생성하는 방법이 제한되어 있기 때문에, 다른 방식과 달리 외부에서 인스턴스를 생성할 수 없습니다. 그리고 멀티스레드 환경에서도 안전합니다. 아래 방식에서는 INSTANCE라는 이름의 열거형 상수가 싱글톤 객체입니다. 필요한 경우에는 해당 상수에 메서드를 추가하여 동작을 정의할 수 있습니다. 이 방식은 가장 간결하고, 코드도 간결하며, 직렬화 문제와 멀티스레드 환경에서 안전하게 동작합니다.
public enum Singleton {
INSTANCE;
public void doSomething() {
// 싱글톤 객체의 동작을 정의합니다.
}
}
'JAVA > 디자인 패턴' 카테고리의 다른 글
[Design Pattern] Bridge(브릿지) 패턴이란? (0) | 2023.04.30 |
---|---|
[Design Pattern] Adapter(어댑터) 패턴이란? (0) | 2023.04.30 |
[Design Pattern] Abstract Factory(추상 팩토리) 패턴이란? (1) | 2023.04.25 |
[Design Pattern] Factory Method(팩토리 메서드) 패턴이란? (0) | 2023.04.25 |
[Design Pattern] Builder(빌더) 패턴이란? (0) | 2023.04.24 |