05. SingleTon Pattern

2025. 8. 10. 00:27
반응형
Generated Document

싱글톤 패턴, 왜 중요하고 어떻게 쓰일까?

SingleTon Pattern 개발자들이 가장 접하기 쉬운 디자인 패턴이라고 생각한다.
특히 Spring 프레임워크의 빈이 기본적으로 SingleTon 스코프를 사용하고, 우리나라의
대부분의 개발자들이 Spring 프레임워크에 대한 사용 경험이 있기 때문에 이 패턴에 대해서 접하기 쉽다고 생각한다.

싱글톤 패턴의 핵심: 단 하나의 인스턴스

싱글톤 패턴의 가장 큰 특징은 특정 클래스의 객체 인스턴스가 하나만 만들어지도록 보장하는 것이다.
이 유일한 인스턴스는 애플리케이션 내에서 공유되며, 전역적으로 접근이 가능하다.

이런 특징 덕분에 다음과 같은 경우에 주로 사용이 된다.

  1. 리소스 관리

    데이터베이스 커넥션 풀이나 스레드 풀처럼 여러 곳에서 공유하며 접근해야 하는 객체.

  2. 설정 관리

    애플리케이션의 설정 정보를 담고 있는 객체

  3. 프레임워크 내부

    프레임워크가 의존성 주입을 통해 하나의 객체만 사용하도록 강제할 때

고전적인 싱글톤 패턴 구현

유일한 인스턴스를 보장하면서도 외부에서 함부로 생성하지 못하게 하는 것을
구현하기 위해 다음과 같은 3가지 요소를 사용한다.

  1. Private 생성자

new 키워드를 사용해 객체를 생성하지 못하도록 막는다.

  1. static 자기 자신 참조 변수

    클래스 내부에 자기 자신의 인스턴스를 저장할 static 타입의 전역 변수를 선언

  2. public static 인스턴스 반환 메서드

    외부에서 유일한 인스턴스에 접근할 수 있는 통로

인스턴스 반환 메서드는 다음과 같은 동작을 한다.

  1. 만약 인스턴스가 아직 생성되지 않았다면, private 생성자를 호출하여 인스턴스를 새로 만든다.
  2. 만약 인스턴스가 이미 존재한다면, 기존에 생성된 인스턴스를 반환

public class SingleTon{
	private static SingleTon uniqueInstance;
	
	private SingleTon(){}
	
	private static SingleTon getInstance(){
		if ( uniqueInstance == null ){
		     uniqueInstance = new SingleTon();
		}
		return uniqueInstance;
	}
}

고전적인 싱글톤 패턴에서의 문제점

멀티스레드 환경에서 SingleTon Pattern 의 문제점이 생길 수 있다.
위의 getInstance() 처럼 단일 객체 생성 및 호출 하기 위한 메서드가 서로 다른
Thread 에서 한꺼번에 호출되어 uniqueInstance 가 두번 생성되는 일이 발생할 수 있다.


Example

  1. 스레드 A가 *getInstance()* 호출

    *uniqueInstance* 가 아직 *null* 인 것을 확인

  2. 스레드 B가 *getInstance()* 호출

    스레드 A가 인스턴스를 생성하기 전에 스레드 B도 uniqueInstance가 null인 것을 확인

  3. 스레드 A가 인스턴스 생성

    new Singleton()을 호출하여 인스턴스를 생성하고 uniqueInstance에 할당

  4. 스레드 B가 인스턴스 생성

    스레드 B도 이어서 new Singleton()을 호출하여 새로운 인스턴스를 생성하고 uniqueInstance에 덮어쓴다.

결국 객체 생성에 대한 동기화가 문제인 것이다.

고전적인 싱글톤 패턴에서의 문제점 해결


1. 속도가 그렇게 중요하지 않다.

Application 에 큰 부담을 주지 않는다면 그냥 두는 것도 좋다.


2. 인스턴스 필요할 때마다 생성한다.

public class SingleTon{
	private static SingleTon uniqueInstance = new SingleTon();
	private SingleTon(){}
	public static SingleTon getInstance(){
		return uniqueInstance;
	}
}

멀티 스레딩 환경에서 안전하게 싱글톤 인스턴스를 보장한다.

별도의 동기화 로직이 필요 없지만, 객체 생성 비용이 크거나 메모리 사용량이 많은 경우
전역 변수로 사용하는 것 처럼 메모리 낭비가 발생할 수 있다.


3. synchronized

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {}

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

멀테 스레드 환경에서 완벽하게 하나의 인스턴스만 생성되는 것을 보장한다.
하지만 메서드 전체에 LOCK 을 거는 것이기 때문에 객체가 생성된 이후에도 모든 스레드들은
getInstance() 를 호출할 때마다 락을 기다려야한다.

즉, 동기화 작업이 매번 발생한다는 것이고 이는 성능저하를 발생한다.


4. DCL 사용

public class SingleTon{
	private volatile static SingleTone uniqueInstance;
	
	private SingleTon(){}
	
	public static SingleTon getInstance(){
		if( uniqueInstance == null ){
			synchronized( SingleTone.class ) {
				if ( uniqueInstance == null ) {
						uniqueInstance = new SingleTon();
				} 
			} 
		}
		return uniqueInstance;
	}
}

volatile 를 사용하여 CPU 메인 메모리에 즉시 기록할 수 있도록 보장하며, 읽기 작업 발생 시
메인메모리에서 가장 최신 값을 읽어 오도록 보장한다.
즉, 한 스레드의 쓰기 작업이 다른 스레드에 즉시 보이도록 하여 데이터 불일치 문제를 방지한다.

이를 통해 멀티 스레드 환경에서 스레드가 항상 최신 상태의 데이터를 공유하도록 보장한다.
객체를 불러올때, 객체가 할당되지 않는 경우에만 synchronized 를 사용하여 동기화를 한다.

의문점 : 전역 변수로 사용하면 안되나?


Question :

단 하나의 인스턴스를 사용하기 위해서 싱글톤 대신 전역 변수를 사용하여
객체 어디서든지 만들어진 인스턴스를 사용하게 하면 되지 않나?


Answer :

전역 변수에 객체를 할당하여 사용하면 어플리케이션과 객체의 생명주기가 같아진다.
즉, 어플리케이션이 종료되어야 객체에 할당된 메모리가 해제 된다는 것 인데,
만약 해당 객체가 자주 사용되지 않거나 아예 사용되지 않는다면 리소스 낭비가 된다.

반면 싱글턴 패턴은 게으른 초기화 방식을 사용한다.
객체가 처음으로 요청 될때 인스턴스를 생성하는 방식이다.

뿐만아니라 다른 변수와 이름이 충돌할 가능성이 있고,
객체 추적이 어려워 디버깅에 문제가 있으며 생성자에 대한 제어를 보장하지 못하게 된다.

최종 정리

글톤 패턴은 애플리케이션에서 유일한 객체 인스턴스를 보장하는 디자인 패턴이다.
주로 데이터베이스 연결이나 스레드 풀처럼 여러 곳에서 공유하는 자원 관리에 사용된다.

전역 변수와 비슷해 보이지만, 싱글톤은 다음과 같은 장점이 있다.

  • 게으른 초기화

    객체가 처음 필요할 때 생성되어 메모리를 효율적으로 사용한다.

  • 제어

    private 생성자를 통해 객체 생성을 막아 유지보수와 디버깅을 쉽게 한다.

멀티스레드 환경에서도 안전하게 사용할 수 있도록 다양한 구현 방법

  • Eager Initialization:

    클래스 로딩 시 미리 객체를 생성한다. 구현이 간단하고 안전하지만, 게으른 초기화의 장점은 없다.

  • synchronized 키워드:

    getInstance() 메서드에 동기화를 적용하여 한 번에 하나의 스레드만 접근하도록 보장한다. 안전하지만 성능 저하가 있을 수 있다.

  • DCL (Double-Checked Locking):

    인스턴스가 없을 때만 동기화하여 성능 오버헤드를 줄인다.

반응형

BELATED ARTICLES

more