카테고리 없음

[Java/개발자면접] 디자인패턴 '싱글턴' (싱글턴 구현 방식, 주의점, 단점)

비타민찌 2022. 11. 28. 10:54
728x90

1. 싱글턴이란?

싱글톤(Singleton) 패턴은 어떤 클래스의 인스턴스가 오직 하나임을 보장하며, 이 인스턴스에 접근할 수 있는 전역적인 접촉점을 제공하는 패턴입니다. 즉, 프로그램 시작부터 종료 시까지 어떤 클래스의 인스턴스가 메모리 상에 단 하나만 존재할 수 있게 하고, 이 인스턴스에 대해 어디에서나 접근할 수 있도록 하는 패턴입니다.

 
싱글톤 패턴이 고안된 이유:

개발을 하다보면 어떤 클래스에 대해 단 하나의 인스턴스만을 갖도록 하는 것이 좋은 경우가 있습니다. 예를 들어, 로그를 찍는(Logging) 객체라던가 쓰레드 풀, 윈도우 관리자 등 여러 객체를 관리하는 역할의 객체는 프로그램 내에서 단 하나의 인스턴스를 갖는 것이 바람직합니다.

 

어떻게 접근할 수 있나?

전역변수? 틀린 말은 아니지만.. 더 좋은 방법은 클래스 자신이 자기의 유일한 인스턴스로 접근하는 방법을 자체적으로 관리하는 것입니다. 생성자를 private하게 만들어서 클래스 외부에서는 인스턴스를 생성하지 못하게 차단하고, 내부에서 단 하나의 인스턴스를 생성하여 외부에는 그 인스턴스에 대한 접근 방법을 제공할 수 있습니다.

싱글톤 패턴(Singleton)과 정적 클래스(Static) 차이

 

JAVA 디자인패턴 | 싱글톤 패턴(Singleton)과 정적 클래스(Static) 차이. 다중 스레드, 생성자

자바 디자인 패턴 두번째 포스팅 - 싱글톤 패턴이란 어떤 것인지 - 싱글톤 패턴은 어떤 상황에 쓰는 것인지...

blog.naver.com

 

2. 싱글톤(Singleton) 패턴을 구현하는 6가지 방법

참고: https://readystory.tistory.com/116

방법들의 공통적 특징:

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

전부 구현할 줄 알아야 한다기 보다는 한번 읽어서 이해하고, 싱글톤의 핵심과 한계 정도만 알아두면 좋을 것 같다.

 

(1) Eager Initialization (낭비 & Exception에 대한 Handling x)

public class Singleton {
    
    private static final Singleton instance = new Singleton();
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}
 

싱글톤 클래스의 인스턴스를 클래스 로딩 단계에서 생성하는 방법.

이 방법을 사용할 때는 싱글톤 클래스가 다소 적은 리소스를 다룰 때여야 합니다. File System, Database Connection 등 큰 리소스들을 다루는 싱글톤을 구현할 때는 위와 같은 방식보다는 getInstance() 메소드가 호출될 때까지 싱글톤 인스턴스를 생성하지 않는 것이 더 좋습니다. 게다가 Eager Initializaion은 Exception에 대한 Handling도 제공하지 않습니다.

 

(2) Static Block Initialization (낭비)

public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    static{
        try{
            instance = new Singleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    public static Singleton getInstance(){
        return instance;
    }
}
 

방식 1에서 exception handling은 제공되었지만 여전히 낭비..

 

(3) Lazy Initialization (나중에 초기화하는 방법)

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

이는 global access 한 getInstance() 메소드를 호출할 때에 인스턴스가 없다면 생성하는 방식 입니다.

그러나 multi-thread 환경에서 동기화 문제가 있습니다. 만약 인스턴스가 생성되지 않은 시점에서 여러 쓰레드가 동시에 getInstance()를 호출한다면 예상치 못한 결과를 얻을 수 있을뿐더러, 단 하나의 인스턴스를 생성한다는 싱글톤 패턴에 위반하는 문제점이 야기될 수 있습니다. 그래서 이 방법으로 구현을 해도 괜찮은 경우는 single-thread 환경이 보장됐을 때입니다.

 

(4) 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;
    }
    
}
 

3번의 문제를 해결하기 위한 방법으로, getInstance() 메소드에 synchronized를 걸어두는 방식입니다. synchronized 키워드는 임계 영역(Critical Section)을 형성해 해당 영역에 오직 하나의 쓰레드만 접근 가능하게 해 오직 하나의 스레드만 접근 가능하게 해 줍니다. 그러나 synchronized 키워드 자체에 대한 비용이 크기 때문에 싱글톤 인스턴스 호출이 잦은 어플리케이션에서는 성능이 떨어지게 됩니다.

 

(5) Bill Pugh Singleton

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

Implementaion private inner static class를 두어 싱글톤 인스턴스를 갖게 합니다. 이때 1번이나 2번 방식과의 차이점이라면 SingletonHelper 클래스는 Singleton 클래스가 Load 될 때에도 Load 되지 않다가 getInstance()가 호출됐을 때 비로소 JVM 메모리에 로드되고, 인스턴스를 생성하게 됩니다. 아울러 synchronized를 사용하지 않기 때문에 4번에서 문제가 되었던 성능 저하 또한 해결됩니다.

 

(6) Enum Singleton

public enum EnumSingleton {
 
    INSTANCE;
    
    public static void doSomething(){
        ~ do something ~
    }
}
 

앞서 1~5번에서 살펴본 싱글톤 방식은 사실 완전히 안전할 수 없습니다. 왜냐하면 Java의 Reflection을 통해서 싱글톤을 파괴할 수 있기 때문입니다.

그러나 이 방법 또한 1, 2번과 같이 사용하지 않았을 경우의 메모리 문제를 해결하지 못한 것과 유연성이 떨어진다는 면에서의 한계를 지니고 있습니다.

 

 

3. 싱글톤 단점

  • 코드 자체가 많아짐
  • 의존 관계상 클라이언트가 구체 클래스 의존 (DIP, OCP 위반)
  • 유연성이 떨어짐

→ 스프링은 '스프링 컨테이너'로 이를 해결해줌

 

 

4. 싱글톤 방식 주의점

  • 객체 인스턴스를 한개 만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게 설계하면 안된다.
  • 가급적 읽기만 가능해야 한다.
  • 필드 대신 자바에서 공유되지 않는 지역변수나 스레드풀 등을 사용해야 한다.

 

자, 그런데 스프링 싱글톤 패턴 반면 스프링 싱글톤은 클래스 자체에 의해서가 아니라 '스프링 컨테이너(Bean Factory/Application Context)'에 의해 구현된다. 싱글턴 패턴과 어떤 차이가 있을까?

 

728x90