본문 바로가기
ComputerScience/Design Pattern

Singleton Pattern

by 규난 2023. 1. 30.
728x90

Singleton Pattern 이란?

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴입니다.
  • 인스턴스 생성을 제어할 수 있어야 합니다.
  • 1개뿐인 인스턴스에 쉽게 접근할 수 있어야 합니다.

Singleton pattern의 장점

  • 하나의 인스턴스만을 생성하여 메모리 낭비를 방지할 수 있습니다.
  • 싱글톤으로 구현한 인스턴스는 전역적으로 사용이 가능하여 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽습니다

Singleton pattenr의 단점

  • SOLID 원칙인 DIP, OCP 위반 가능성이 높아지고 이를 위반하기 때문에 유지 보수가 힘들고 테스트가 어려워집니다.
  • 제대로 구현하지 않으면 멀티 스레드 환경에서 여러 인스턴스개 생성되는 문제가 발생할 수 있습니다.

Singleton pattern 예제

Eager initialization

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

싱글톤 객체를 미리 생성하는 기본적인 싱글톤 방식입니다. 이 방식은 클래스 로더에 의해 클래스가 최초 로딩 될 때 인스턴스가 생성되므로 Thread-safe 하지만 싱글톤 인스턴스를 사용하지 않더라도 메모리에 인스턴스가 올라가기 때문에 메모리 누수가 발생할 수 있어 비효율적인 방식입니다.

 

Lazy initialization

public class Singleton {

    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            // 초기화를 늦추기위한 null check
            instance = new Singleton();
        }
        return instance;
    }
}

이 방식은 싱글톤 인스턴스가 사용되는 시점에 인스턴스를 생성하기 때문에 메모리 누수를 방지할 수 있는 방식입니다.

하지만 multl thread 환경에서 Thread-safe 하지 않아 여러 개의 인스턴스가 생길 수 있습니다.

 

Thread-safe Lazy initialization

package singleton;

public class Singleton {

    private static Singleton instance;

    private Singleton() {}

    public synchronized static Singleton getInstance() 
        if (instance == null) {
           instance = new Singleton();
        }
        return instance;
    }
}

Lazy initialization 방식에서 가장 간단하게 Non Thread-safe 문제점을 해결한 방식입니다.

이 방식은 간단하지만 하나의 thread가 singleton 인스턴스를 사용하고 있을 때 다른 thread는 singleton 인스턴스를 사용하지 못하고 기다리게 되어 굉장히 느리게 처리되는 방식입니다.

 

Double-checked Locking + Thread-safe Lazy initialization

package singleton;

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;
    }
}

Thread-safe Lazy initialization 방식에서 instance가 null 일 경우에만 synchronized keyword를 사용해 synchronization keyword의 영향받는 곳을 최소화하여 성능을 개선한 방식입니다.

 

이 방식에서 멤버 필드에 volatile keyword를 사용한 이유는 여러 cpu에서 동시에 synchronized block을 실행하는 순간 메모리 미스매치 현상이 나올 수 있는데 volatile keyword를 사용하여 cpu의 캐시메모리를 사용하지 않고 항상 main memory에 접근하여 읽기 시점과 쓰기 시점의 불일치를 해결하여 메모리 미스매치 현상을 없애기 위해 사용하였습니다.

 

메모리 미스매치란 cpu는 main memory에 인스턴스가 null인 경우 synchronized block을 실행하고 cpu 캐시메모리에 데이터를 저장 후 main memory에 데이터를 동기화 하려는 순간 또 다른 cpu에서 main memory에 인스턴스를 null로 읽어드려 또 다시 synchronized block을 실행하게 되어 여러 인스턴스가 생기는 현상을 말합니다.

 

initialization on demand holder idiom

public class Singleton {

    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

이 방식은 class loader가 인스턴스가 실제 사용되는 시점에 JVM에 올리는 방식입니다.

class loader가 class를 loading 하는 시점에는 Thread-safe를 보장하기 때문에 synchronized, volatile keyword를 사용하지 않아도 되며 final keyword를 사용하여 다시 값이 할당되지 않도록 방지할 수 있습니다. 또한 개발자가 동기화 문제를 해결하기 위한 코드가 줄어들기 때문에 가장 많이 사용되는 방식입니다.

728x90

'ComputerScience > Design Pattern' 카테고리의 다른 글

Adaptor Pattern  (0) 2023.03.06
Facade Pattern  (0) 2023.02.26
Strategy Pattern  (0) 2023.02.20
디자인 패턴 - 디자인 원칙  (0) 2023.02.14
Builder Pattern  (0) 2023.02.01