싱글톤 구현 방법

JUHYUN·2022년 12월 2일
0

1. 단순 메서드 호출

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        
    }

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

✏ 특징

싱글톤 인스턴스 생성 여부를 확인하여 없다면 새로 생성하고 있다면 만들어진 인스턴스를 반환합니다.

❗ 문제점

멀티스레드 환경에서 2개 이상의 인스턴스가 만들어질 수 있습니다. => 원자성이 결여되어있습니다.

단순 메서드 호출 싱글톤 문제점

싱글톤 인스턴스가 생성되지 않은 상태에서 2개의 쓰레드가 위와 같은 순서로 싱글톤 인스턴스에 접근하게 된다면 싱글톤 인스턴스가 2번 초기화가 될 수 있습니다.


2. synchronized 키워드를 사용하여 생성

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

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

✏ 특징

먼저 접근한 쓰레드에게 getInstance() 메서드가 인스턴스를 반환할 때까지 다른 쓰레드가 접근할 수 없도록 lock을 걸 수 있습니다.

❗ 문제점

getInstance() 메서드를 호출할 때마다 lock이 걸려서 성능저하가 될 수 있습니다.

synchronize 키워드와 성능
synchronize 키워드를 사용하면 자바 내부적으로 메서드나 변수를 처리하기 위해 block과 unblock을 처리합니다. 이 과정을 실행할 때 쓰레드는 많은 비용이 드는 인스트럭션 재배열 작업이 필요하기 때문에 synchronize 메서드가 많이 호출되면 프로그램 성능이 저하될 수 있습니다.


3. 정적(static) 멤버

public class Singleton {

    private final static Singleton instance = new Singleton();

    private Singleton() {
    
    }

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

✏ 특징

  • 프로그램이 실행되는 런타임에 싱글톤 인스턴스를 만들지 않습니다.
  • 최초에 JVM에 의해 클래스가 로딩될 때 static 멤버로 설정해둔 싱글톤 인스턴스를 생성해둡니다.
  • 런타임에 getInstance() 메서드는 이미 만들어진 인스턴스를 반환만 해줍니다.

❗ 문제점

싱글톤 인스턴스가 필요하지 않은 경우에도 항상 인스턴스가 만들어지기 때문에 불필요한 자원낭비라는 문제점이 있습니다.


4. 정적(static) 블록

public class Singleton {

    private static Singleton instance = null;

    static {
        instance = new Singleton();
    }

    private Singleton() {
        
    }

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

특징과 문제점은 3번 정적(static) 멤버와 같습니다.


5. 정적(static) 멤버와 Lazy Holder(중첩 클래스)

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

✏ 특징

  • singleInstanceHolder의 인스턴스는 Singleton 클래스 안에서 생성되지 않으므로 singleInstanceHolderSingleton 클래스 로딩 시점에 로딩되지 않습니다.
  • 위의 특징을 이용하여 getInstance()의 메서드가 호출될 때까지 싱글톤 인스턴스의 생성을 미룰 수 있습니다.
  • 클래스 로딩은 JVM에 의하여 thread-safe가 보장되기 때문에 synchronize 키워드 없이도 멀티쓰레드 환경에서도 안전합니다.

❗ 문제점

  • 역직렬화 수행시 새로운 객체 생성
  • 리플렉션을 이용해 내부 생성자 호출 가능

6. Double-Checked Locking (DCL, 이중 확인 잠금)

public class Singleton {

    private volatile Singleton instance;

    private Singleton() {
        
    }

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

✏ 특징

  • 인스턴스 생성 여부를 2번 확인합니다.
    • synchronize 키워드로 잠금 전에 확인
    • synchronize 키워드로 잠금 후 인스턴스를 생성하기 전에 확인
  • 위와 같이 2번 확인하게 되면 인스턴스가 존재하지 않을 때만 lock이 걸리기 때문에 단순히 synchronize 키워드만을 사용했을 때의 성능저하 문제점을 해결할 수 있습니다.

volatile 키워드
멀티쓰레드 환경에서 각각의 쓰레드는 변수를 메인메모리가 아닌 캐시메모리에서 가져오게 됩니다. 따라서 변수 공유가 되지 않는다는 문제점이 발생할 수 있습니다. 하지만 volatile 키워드를 쓰게 되면 변수를 메인 메모리를 기반으로 저장하고 읽어오게 만들기 때문에 위와 같은 문제가 생기지 않습니다.

❗ 문제점

  • 순서 일관성의 문제가 아니라 최적화를 통해 코드가 옮겨지는 문제
  • 많은 JVM이 volatile에 대한 순서 일관성조차 제대로 구현하고 있지 않다.
    => 컴파일러가 어떻게 내부 코드를 재해석하느냐에 따라서 singletonResource 변수에 volatile 키워드를 붙이는 것은 효과가 전혀 없을 수 있다.

7. enum

public enum SingletonEnum {
    INSTANCE;
    public void singletonMethod() {

    }
}

✏ 특징

  • enum은 자바 언어 적으로 인스턴스가 1개임이 보장됩니다.
  • enum 인스턴스는 기본적으로 thread-safe가 보장되기 때문에 싱글톤 인스턴스로 활용될 수 있습니다.
  • enum은 기본적으로 serializable가 가능합니다. 따라서 readObject() 와 같은 Serializable interface를 구현할 필요가 없으므로 역직렬화시 새로운 객체가 생성되지 않음을 보장합니다.
  • enum의 생성자는 접근제한자는 기본적으로 private 이라 Reflection을 통한 생성자 접근이 원천적으로 차단됩니다. 따라서 Reflection으로 외부에서 enum class를 인스턴스화 할 수 없습니다.

Effective Java 의 저자 조쥬아 블로크에 따르면 싱글톤을 구현하기 가장 좋은 방법은 enum을 사용하는 방법이라고 합니다.


🖇 Reference

  1. [Java] java synchronized 동기화
  2. 8. synchronized는 제대로 알고 써야 한다
  3. 싱글턴 패턴의 다양한 구현 방법을 알아보자.
  4. 클래스는 언제 로딩되고 초기화되는가? (feat. 싱글톤)
  5. 디자인 패턴으로 알아본 Double Checked Lock(DCL)
  6. Double-checked Locking Pattern (DCLP) 을 쓰지 말아야 하는 이유
  7. [Java & Kotlin] enum class가 완벽한 싱글톤이라 불리는 이유
  8. [Java] Enum에 대해
  9. [Java] Enum과 싱글톤(Singleton)
profile
행복과 같은 속도를 찾는 개발자

0개의 댓글