싱글톤 패턴

강준우·2023년 2월 4일
0

싱글톤(Singleton) 패턴


객체의 인스턴스가 오직 1개만 생성되는 패턴.

싱글톤(Singleton) 패턴 장점


* 메모리 측면

최초 한번의 new 연산자를 통해 고정된 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다. 또한 이미 생성된 인스턴스를 활용해 속도 측면에서도 이점이 있다.

* 데이터 공유 측면

싱글톤 인스턴스가 전역으로 사용되기 때문에 다른 클래스의 인스턴스들이 접근하여 사용하기 용이하다. 단, 여러 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 동시 접근하면 동시성 문제가 발생할 수 있어서 유의해서 설계해야 한다.

싱글톤(Singleton) 패턴 단점


  • 싱글톤 패턴 구현 시 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의조하게 된다. 따라서 DIP를 위반하고, OCP도 위반할 가능성이 크다.
  • 테스트 코드 작성이 어려워진다.
  • 내부 속성을 변경하거나 초기화가 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 유연성이 떨어진다.
    -> 싱글톤 패턴은 안티패턴으로 불린다.

싱글톤(Singleton) 패턴 구현방법


각각의 방식에 공통적으로 갖는 특징이 있다.

  • private 생성자만을 정의해 외부 클래스로부터 인스턴스 생성을 차단한다.
  • 싱글톤을 구현하고자 하는 클래스 내부에 멤버 변수로써 private static 객체 변수를 만든다.
  • public static 메소드를 통해 외부에서 싱글톤 인스턴스에 접근할 수 있도록 접점을 제공한다.

Eager Initialization


public class Singleton {
    
    private static final Singleton instance = new Singleton();
    
    // private constructor to avoid client applications to use constructor
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}
  • 싱글톤 클래스의 인스턴스를 클래스 로딩 단계에서 생성하는 방법이다.

  • 하지만 어플리케이션에서 해당 인스턴스를 사용하지 않더라도 인스턴스를 생성하기 때문에 자칫 낭비가 발생할 수 있다.

Static Block Initialization


public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    //static block initialization for exception handling
    static{
        try{
            instance = new Singleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    public static Singleton getInstance(){
        return instance;
    }
}
  • Eager Initialization과 유사하지만 static block을 통해서 Exception Handling에 대한 옵션을 제공한다.
  • Eager Initialization과 마찬가지로 클래스 로딩 단계에서 인스턴스를 생성하기 때문에 여전히 큰 리소스를 다루는 경우에는 적합하지 않다.

Lazy Initialization


public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • 앞선 두 방식과는 달리 나중에 초기화하는 방법이다.
  • Multi-Thread 환경에서 만약 인스턴스가 생성되지 않은 시점에 여러 쓰레드가 동시에 getInstance()를 호출한다면 예상치 못한 결과를 얻을 수 있을뿐더러, 단 하나의 인스턴스를 생성한다는 싱글톤 패턴에 위반하는 문제점이 야기될 수 있다.
  • 따라서 Single-Trhead 환경에서 사용하는 것이 권장된다.

Thread Safe Singleton


public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    
}
  • 앞의 문제를 해결하기 위해 getInstance() 메소드에 synchronized를 걸어두는 방식이다.
  • synchronized 키워드 자체에 대한 비용이 크기 때문에 싱글톤 인스턴스 호출이 잦은 어플리케이션에서는 성능이 떨어지게 된다.
public static Singleton getInstance(){
    if(instance == null){
        synchronized (Singleton.class) {
            if(instance == null){
                instance = new Singleton();
            }
        }
    }
    return instance;
}
  • 따라서 getInstance() 메소드 수준에 lock을 걸지 않고 instance가 null일 경우에만 synchronized가 동작하도록 한다.

Bill Pugh Singleton Implementaion


public class Singleton {
 
    private Singleton(){}
    
    private static class SingletonHelper{
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}
  • inner static helper class를 사용하는 방식으로, 앞선 방식이 안고 있는 문제점들을 대부분 해결하여 현재 가장 널리 쓰이는 싱글톤 구현 방법이다.
  • 동기화 작업을 JVM에 위임하여, 만약 다른 쓰레드가 getInstance() 메서드를 호출할 때에도 static final 로 선언된 INSTANCE 가 이미 JVM 메모리에 올라와 있기 때문에 싱글톤 객체를 한 번만 생성할 것이라고 JVM이 보장해준다.
  • 하지만, Java의 Reflection을 사용하면 private 생성자, 메서드에 접근이 가능해지며 단 하나의 객체라는 조건을 깨뜨려버린다.

Enum Singleton


public enum EnumSingleton {
 
    INSTANCE;
    
    public static void doSomething(){
        //do something
    }
}
  • 사용하지 않았을 경우의 메모리 문제를 해결하지 못하고 유연성이 떨어진다.
profile
강준우

0개의 댓글

관련 채용 정보