관리 메뉴

SW

[개발서적] 헤드퍼스트 디자인 패턴 Ch5. 싱글턴(Singleton) 패턴 본문

개발서적/헤드퍼스트 디자인패턴

[개발서적] 헤드퍼스트 디자인 패턴 Ch5. 싱글턴(Singleton) 패턴

SWKo 2024. 8. 12. 21:45

1. 싱글턴 패턴 vs 전역 변수

전역 변수의 단점은 애플리케이션 시작 시 객체가 생성되어 자원을 낭비할 수 있다는 것입니다.

예를 들어, 전역 변수에 자원을 많이 차지하는 객체를 대입했지만, 애플리케이션 종료까지 사용되지 않는다면 쓸모없는 자원 낭비가 됩니다. 이를 방지하기 위해 싱글턴 패턴을 사용하면 필요할 때만 객체를 생성할 수 있습니다.

2. 고전적인 싱글턴 패턴 구현법

  • 클래스 내에서 자신의 유일한 인스턴스를 정적 변수로 보유
    • 클래스 내부에 static 변수를 사용하여 자신의 인스턴스를 저장합니다.
  • 생성자를 private으로 설정
    • 외부에서 객체를 생성하지 못하도록 생성자를 private으로 선언합니다.
    • 이렇게 하면 클래스 외부에서는 새로운 인스턴스를 만들 수 없습니다.
  • 유일한 인스턴스에 접근할 수 있는 정적 메서드 제공
    • 정적 메서드를 통해 외부에서 이 클래스의 인스턴스에 접근할 수 있도록 합니다.
    • 이 메서드에서는 객체가 이미 생성되었는지 확인한 후, 생성되지 않았다면 객체를 생성하고, 생성된 인스턴스를 반환합니다.
public class Singleton {
    // 유일한 인스턴스를 정적 변수로 선언
    private static Singleton uniqueInstance;

    // private 생성자: 외부에서 인스턴스 생성을 막음
    private Singleton() {}

    // 유일한 인스턴스를 반환하는 정적 메서드
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton(); // 인스턴스가 없으면 생성
        }
        return uniqueInstance; // 이미 생성된 인스턴스를 반환
    }
}

3. 초콜릿 보일러

초콜릿 보일러는 초콜릿과 우유를 특정 조건에서만 혼합하고 가열하는 기계입니다.

하지만 두 개 이상의 인스턴스가 동시에 작동하면 시스템이 잘못된 상태에 빠질 수 있어 문제가 발생합니다.

 

이 문제를 해결하기 위해 싱글턴 패턴을 적용합니다.

싱글턴 패턴을 사용하면 초콜릿 보일러 클래스의 인스턴스가 하나만 생성되고, 이 인스턴스에만 접근할 수 있습니다.

이를 통해 여러 개의 보일러 인스턴스가 생성되어 발생할 수 있는 문제를 방지할 수 있습니다.

public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;

    // 유일한 인스턴스를 저장할 static 변수
    private static ChocolateBoiler uniqueInstance;

    // private 생성자: 외부에서 인스턴스를 생성하지 못하게 막음
    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    // 유일한 인스턴스를 반환하는 static 메서드
    public static ChocolateBoiler getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new ChocolateBoiler();
        }
        return uniqueInstance;
    }

    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
            // 보일러에 초콜릿과 우유를 채움
        }
    }

    public void drain() {
        if (!isEmpty() && isBoiled()) {
            // 보일러를 비움
            empty = true;
        }
    }

    public void boil() {
        if (!isEmpty() && !isBoiled()) {
            // 보일러를 가열
            boiled = true;
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}

 

4. 싱글턴 패턴의 정의

싱글턴 패턴(Singleton Pattern)은 클래스 인스턴스를 하나 만들고, 그 인스턴스로의 전역 접근을 제공합니다.

5. 멀티스레딩 문제 해결하기

멀티쓰레드 환경에서 동시에 여러 쓰레드가 getInstance() 메서드를 호출할 경우, 여러 개의 인스턴스가 생성될 수 있는 위험이 있습니다.

 

메서드 동기화 (synchronized)

getInstance() 메서드 전체를 synchronized 키워드를 사용해 동기화하여, 한 번에 하나의 쓰레드만 메서드를 실행할 수 있게 합니다.

public static synchronized ChocolateBoiler getInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new ChocolateBoiler();
    }
    return uniqueInstance;
}

 

이 방법은 안전하지만, 모든 접근이 동기화되어 성능이 저하(100배 차이)될 수 있습니다.

 

6. 더 효율적으로 멀티스레딩 문제 해결하기

방법 1

getInstance() 메서드의 속도가 중요하지 않다면, 동기화를 그대로 사용해도 괜찮습니다.

동기화는 구현이 간단하고 효율적일 수 있지만, 성능 저하가 발생할 수 있습니다.

만약 getInstance()가 애플리케이션에서 성능 병목이 된다면, 다른 해결 방법을 고려해야 합니다.

 

방법 2

싱글턴 인스턴스를 필요할 때 생성하지 말고, 처음부터 생성하는 방법입니다.

정적 초기화 부분에서 Singleton 인스턴스를 미리 생성하여, 멀티쓰레드 환경에서도 안전하게 사용할 수 있습니다.

getInstance() 메서드는 이미 생성된 인스턴스를 반환하기만 하면 되므로 간단하고 효율적입니다.

public class Singleton {
    // 클래스 로딩 시점에 인스턴스를 생성
    private static final Singleton uniqueInstance = new Singleton();

    // private 생성자: 외부에서 인스턴스 생성 불가
    private Singleton() {}

    // 이미 생성된 인스턴스를 반환
    public static Singleton getInstance() {
        return uniqueInstance;
    }
}

 

방법 3

DCL(Double-Checked Locking)을 사용할 수 있습니다.

getInstance() 메서드에서 인스턴스가 이미 생성되었는지 먼저 확인하고, 생성되지 않은 경우에만 동기화 블록에 들어가 인스턴스를 생성합니다.

이로써, 인스턴스가 처음 생성될 때만 동기화가 일어나고, 그 이후에는 동기화 없이 인스턴스에 접근할 수 있어 성능이 향상됩니다.

public class Singleton {
    // volatile 키워드를 사용하여 인스턴스 변수 선언
    private volatile static Singleton uniqueInstance;

    // private 생성자: 외부에서 인스턴스 생성 불가
    private Singleton() {}

    // 인스턴스 생성 메서드
    public static Singleton getInstance() {
        if (uniqueInstance == null) { // 인스턴스가 없으면
            synchronized (Singleton.class) { // 클래스에 대해 동기화
                if (uniqueInstance == null) { // 다시 한 번 인스턴스가 없는지 확인
                    uniqueInstance = new Singleton(); // 인스턴스 생성
                }
            }
        }
        return uniqueInstance; // 이미 생성된 인스턴스를 반환
    }
}

 

  • volatile 키워드: uniqueInstance 변수를 volatile로 선언하여, 인스턴스가 생성되는 과정을 다른 쓰레드들이 정확하게 인식할 수 있도록 합니다.
  • 이중 검증: getInstance() 메서드에서 인스턴스가 null인지 두 번 확인합니다. 처음에는 동기화 없이 확인하고, 두 번째는 동기화 블록 내에서 확인하여 인스턴스가 없는 경우에만 생성합니다.

방법 4

enum을 사용합니다.

enum은 자바 언어 스펙에 의해 인스턴스가 단 한 번만 생성되고, 그 인스턴스는 enum의 생애주기 동안 계속 유지됩니다.

따라서 싱글턴 인스턴스가 하나만 존재하는 것이 자동으로 보장됩니다.

public enum Singleton {
    UNIQUE_INSTANCE;  // enum 상수로 싱글턴 인스턴스 정의

    // 기타 필요한 필드와 메서드를 정의할 수 있습니다.
    public void someMethod() {
        // 싱글턴 객체로 수행할 작업들
        System.out.println("Doing something with the singleton instance.");
    }
}

public class SingletonClient {
    public static void main(String[] args) {
        // Singleton 인스턴스에 접근
        Singleton singleton = Singleton.UNIQUE_INSTANCE;
        
        // 여기서 싱글턴을 사용
        singleton.someMethod();  // "Doing something with the singleton instance." 출력
    }
}

 


https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/

 

Singleton 패턴

앱 전체에서 공유 및 사용되는 단일 인스턴스 - Singleton은 1회에 한하여 인스턴스화가 가능하며 전역에서 접근 가능한 클래스를 지칭한다. 만들어진 Singleton…

patterns-dev-kr.github.io

 

 

Comments