싱글톤 패턴(Singleton Pattern)

윤준혁·2024년 2월 23일

싱글톤 패턴이란?

  • 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
  • 하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수 있지만 그렇게 하지 않고, 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는데 쓰이며 보통 데이터베이스 연결 모듈에 많이 사용

장점

  • 하나의 인스턴스를 기반으로 해당 인스턴스를 다른 모듈들이 공유하여 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어듬
  • "인스턴스 생성에 많은 비용"이 드는 I/O 바운드 작업에 많이 사용

I/O 바운드 : 입력과 출력(I/O)에 의해 프로그램의 실행 속도가 결정되는 상황(디스크 연결, 네트워크 통신, 데이터베이스 연결 등) 즉, CPU의 처리 속도보다 I/O 장치의 속도가 느리기 때문에 프로그램의 실행 속도가 I/O에 의해 제한되는 것

단점

  • 의존성이 높아지며 TDD(Test Driven Development)를 할 때 걸림돌
  • TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 함
  • 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 '독립적인' 인스턴스를 만들기가 어려움

의존성 : 종속성이라고도 하며 A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 된다는 것을 의미

싱글톤 패턴을 구현하는 7가지 방법

1. 단순한 매서드 호출

  • 싱글톤 패턴 생성 여부를 확인하고 싱글톤이 없으면 새로 만들고 있다면 만들어진 인스턴스를 반환
  • 하지만 이 코드는 메서드의 원자성이 결여
  • 멀티스레드 환경에서는 싱글톤 인스턴스를 2개 이상 만들 수 있음

멀티스레드 환경

  • 하나의 프로그램 내에서 여러 개의 작업을 동시에 처리하는 환경
  • 여러 작업을 동시에 처리할 수 있기 때문에, 시간을 효율적으로 활용
  • 각 스레드가 서로에게 영향을 주지 않도록 관리하는 것이 중요하며, 이를 위해 동기화 등의 기법이 필요
public class Singleton {
	private static Singleton instance;
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
    	if (instance == null) {
        	instance = new Singleton();
        }
        return instance;
    }
}

2. synchronized

  • 인스턴스를 반환하기 전까지 격리 공간에 놓기 위해 synchronized 키워드로 잠금
  • 최초로 접근한 스레드가 해당 메소드 호출시에 다른 스레드가 접근하지 못하도록 잠금
  • lock이 걸려있는 메서드를 호출할 때마다 성능저하(인스턴스가 만들어져도 메소드는 호출 가능해서)
public class Singleton {
	private static Singleton instance;
    
    private Singleton() {
    }
    
    public static synchronized Singleton getInstance() {
    	if (instance == null) {
        	instance = new Singleton();
        }
        return instance;
    }
}

3. 정적 멤버

  • 정적(static) 멤버나 블록은 런타임이 아니라 최초에 JVM이 클래스 로딩 때 모든 클래스들을 로드할 때 미리 인스턴스를 생성하는데 이를 이용한 방법
  • 클래스 로딩과 동시에 싱글톤 인스턴스를 만듬. 그렇기 때문에 모듈들이 싱글톤 인스턴스를 요청할 때 만들어진 인스턴스를 반환
  • 불필요한 자원낭비라는 문제점(싱글톤 인스턴스가 필요없는 경우에도 무조건 싱글톤 클래스를 호출해 인스턴스를 만들기 때문)
public class Singleton {
	private final static Singleton instance = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
		return instance;
    }
}

4. 정적 블록

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

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

  • singeInstanceHolder라는 내부클래스를 하나 더 만듦으로써, Singleton클래스가 최초에 로딩되더라도 함께 초기화가 되지 않고, getInstance()가 호출될 때 sigleInstanceHolder 클래스가 로딩되어 인스턴스를 생성하게 됨
Class Singleton {
	private static class singleInstanceHolder {
    	private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
    	return singleInstanceHolder.INSTANCE;
    }
}

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

  • 인스턴스 생성 여부를 싱글톤 패턴 잠금 전에 한 번, 객체를 생성하기 전에 한 번 총 2번 체크하면 인스턴스가 존재하지 않을 때만 잠금을 걸 수 있기 때문에 앞서 생겼던 문제점을 해결 가능
public class Singleton {
	private volatile Singleton instance;
    
    private Singleton() {
    }
    
    public Singleton getInstance() {
    	if (instance == null) {
        	synchronized (Singleton.class) {
            	if (instance == null) {
                	instance = new Singleton();
                }
            }
        }
    }
}

7. enum

  • enum의 인스턴스는 기본적으로 스레드세이프(thread safe)한 점이 보장되기 때문에 이를 통해 생성
public enum SingletonEnum() {
	INSTANCE;
    
    public void oortCloud() {
    }
}

추천 방법

    1. 정적 멤버와 Lazy Holder(중첩 클래스) : 가장 많이 씀
    1. enum : 이펙티브 자바를 쓴 조슈아 블로크가 추천

0개의 댓글