본문 바로가기

JAVA/디자인 패턴

[Design Pattern] Singleton(싱글톤) 패턴이란?

반응형
생성 패턴(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() {
        // 싱글톤 객체의 동작을 정의합니다.
    }
}

 

 

반응형