티스토리 뷰

728x90

 

Singleton Pattern

  • 클래스 인스턴스를 하나만 만드는 패턴
  • 생성자를 private으로 두고, 하나뿐인 인스턴스의 전역 접근을 제공한다.
  • 인스턴스가 필요할 때 클래스에 요청하고, 요청이 들어오면 하나뿐인 인스턴스를 반환한다. → lazy instantiation

 

싱글톤 패턴이 필요한 경우

  • 인스턴스가 하나만 존재해야 하는 경우 사용한다. (스레드 풀, 캐시, 로그 등)
  • 이런 경우 인스턴스가 여러개라면, 자원을 불필요하게 사용하거나 결과가 일관되지 않을 수 있다.
  • 전역 변수를 사용해서 인스턴스를 생성하면, 필요하지 않은 경우에도 인스턴스가 생성되어 자원을 잡아먹을 수 있다.
  • 여러 곳에서 같은 데이터를 공유할 수 있다.

 

 

구조 

 

 

구현 방식

1) 기존 싱글톤 패턴 방식

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 인스턴스가 생성되지 않았다면 생성 후 반환하고, 이미 생성되었다면 그 인스턴스를 바로 반환한다.


  • 멀티스레딩에서 발생하는 문제 

  • 두개의 스레드가 동시에 접근할 경우, 둘다 null인 if문을 만족시키고 서로 다른 인스턴스가 두개 생성될 수 있다.

 

2) synchronized

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • synchronized 키워드를 통해 한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드는 이 메소드를 사용할 수 없다.
  • 즉, 2개의 스레드가 동시에 인스턴스를 생성할 수 없다.

 

  • 성능 저하 문제
    • 메소드가 시작되는 시점에만 동기화가 필요하다.
    • instance에 인스턴스를 대입한 후에는, 동기화가 필요하지 않다.

 

3) 처음부터 인스턴스를 생성

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
  • 실행 중에 인스턴스를 만들지 않고, 클래스가 로드되는 시점에 인스턴스를 생성한다.
  • 이미 인스턴스가 생성된 상태이기 때문에 멀티 스레드에서도 문제가 발생하지 않는다.

 

4) DCL (Double-Checked Locking)

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;
    }
}
  • 인스턴스가 생성되어 있는지 확인 후, 생성되지 않은 경우에만 동기화.
  • 동기화 후 다시 한 번 변수가 null인지 확인하고 인스턴스를 생성한다.
  • volatile은 모든 스레드가 변수를 Main Memory에서 로드할 수 있도록 해준다.
    • volatile을 사용하지 않았을 경우 각각의 스레드는 Main Memory에서 가져온 변숫값을 CPU Cache에 저장한다.
    • 이 CPU Cache는 스레드마다 가지고 있기 때문에 변숫값이 일치하지 않을 수 있다.

 

5) Holder

public class Singleton {
    private Singleton() {}

    private static class LazyHolder {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return LazyHolder.instance;
    }
}
  • Holder 클래스는 Singleton 클래스가 로드될 때에도 메모리에 올라오지 않다가, getInstance()가 호출됐을 때 메모리에 로드되고, 인스턴스를 생성한다.
  • static이기 때문에 하나의 인스턴스만 생성되는 것을 보장하며, final이기 때문에 instance에 새로운 인스턴스가 할당되지 않는다.
  • synchronzied 를 사용하지 않아도 JVM 자체가 보장하는 원자성을 사용하여 Thread-Safe하게 싱글톤 패턴을 구현할 수 있다. → synchronized 키워드의 성능 저하 문제 해결

 

6) enum

  • 위 방식들의 문제점
    • 직렬화/역직렬화 하기 위해 Serializable 인터페이스를 구현하면, 새로운 인스턴스가 생성된다.
    • 리플렉션을 통해 private 멤버에 접근하여 새로운 인스턴스를 생성할 수 있다. (setAccessible)

 

public enum Singleton {
    INSTANCE;
}
  • enum 타입은 기본적으로 직렬화가 가능하므로 Serializable 인터페이스를 구현하지 않는다.
  • 리플렉션을 통해 enum 타입의 생성자에 접근할 수 없다.
  • 인스턴스가 JVM 내에 하나만 존재한다는 것이 보장된다.

 

 

코틀린에서의 싱글톤 패턴

object Singleton
  • object 키워드를 이용해 싱글톤 패턴을 구현할 수 있다.
  • 인스턴스가 한 번만 생성되기 때문에 클래스 내부의 값도 한 번만 생성된다.
  • object 인스턴스에 첫 접근 시 초기화가 수행되고, thread-safe하다.

 

 

싱글톤 패턴 문제점

  1. 단일 책임 원칙(SRP): 싱글톤 인스턴스 자체를 하나만 생성하기 때문에 여러 책임을 지니게 되는 경우가 많다.
  2. 의존 역전 원칙(DIP): 클라이언트 코드가 인터페이스와 같은 추상 클래스가 아닌, 구상 클래스에 의존하게 된다.

 

728x90

'app > design pattern' 카테고리의 다른 글

[디자인 패턴] Factory Pattern  (0) 2023.07.05
[디자인 패턴] Observer Pattern  (0) 2023.07.05
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함