Java Singleton

Chunbae·2024년 12월 19일
0

Java

목록 보기
6/11
post-thumbnail

Singleton?

싱글톤의 정의는 "싱글" 단어 그대로 객체 인스턴스를 단 하나만 생성하는 패턴을 의미합니다.

싱글톤은 주로 메모리 절약과 자주 사용되는 인스턴스를 조회할 때마다 새롭게 생성하지 않고 최초에 생성한 인스턴스를 가져와 활용하는 방법입니다.
우리가 흔히 "클래스 변수"라고 말하는 변수는 클래스내부 전역에서 사용하는 변수를 말하며 해당 변수는 주로 메모리 낭비를 줄이고 값을 공유하기 때문에 사용되는데
이 개념을 객체(클래스)로 확장한 것이 바로 "싱글톤 패턴" 이라고 볼 수 있습니다.


싱글톤은 왜 사용할까?

DB연결을 위한ConnectionPool, ThreadPool과 같은 경우 여러 개의 인스턴스가 생성되면 낭비되는 자원이 발생하게 되고, 예상하지 못한 사이드 이펙트가 발생할 수 있습니다.
이런 경우를 방지하고자 객체를 전역으로 선언하여 이를 공유하고 사용하기 위해 싱글톤 패턴을 이용합니다.

사용 이점

  • 유일한 인스턴스
    전역에 선언 된 객체는 단 하나이기 때문에 일관된 상태를 유지하고 전역 접근이 가능합니다.
  • 메모리 절약
    전역에서 다양한 상황에서 생성자를 통해 객체를 생성해도 새로운 객체가 생성되지 않고 최초에 생성된 객체를 사용하기 때문에 메모리 절약과 오버헤드를 줄일 수 있습니다.
  • 지연 초기화
    객체가 실제로 사용되는 시점에 생성되기 때문에 초기 비용이 줄어듭니다.


싱글톤 패턴의 문제점


모듈간 의존성 증가 - DIP / OCP/ SRP위반

싱글톤 패턴을 이용하는 경우 인터페이스나 추상 클래스가 아닌 구체 클래스를 의존하기 때문에 DIP를 위반하게 됩니다.
새로운 기능이 추가되는 경우 싱글톤 클래스 자체를 수정해야 하는 경우가 발생할 수 있으며 이는 OPC를 위반하며 하나의 객체가 다양한 곳에서 사용되기 때문에 하나의 객체가 다중 책임을 지고 있기 때문에 SRP를 위반하게 됩니다.

해결 방안
인터페이스와 팩토리 패턴을 조합하거나 DI컨테이너를 활용하여 의존성을 완화하여 사용합니다.

TDD의 어려움

싱글톤 패턴은 전역으로 공유되어 사용되기 때문에 텟트 케이스간 상호작용으로 인해 독립성 보장이 어렵습니다. 이로 인해 싱글톤 인스턴스 상태를 초기화 하지않으면 예기치 못한 테스트 결과가 발생할 수 있으며 테스트코드 작성에도 어려움이 있습니다.

해결 방안
싱글톤 인스턴스 자체를 무상태(stateless)로 선언하여 사용하거나, Mocking을 이용하여 의존성 대체하는 등 방법이 있습니다.

유연성 저하

private접근제어자를 활용하여 생성자를 선언하면 상속에 제한되며 확장성에 제약이 생깁니다. 이로 인해 자식 클래스를 사용해야하는 경우 설계가 복잡해지고 유지보수성이 저하됩니다.

안티패턴..?

싱글톤을 잘못 사용되는 경우 코드의 결합도를 증가시키고 위에서 설명한 다양한 문제가 발생하며 SOLID를 위반할 가능성이 높아 안티패턴으로 간주됩니다.



싱글톤 패턴 구현 방법


기본 구현

싱글톤은 단 하나의 인스턴스만을 사용하기 때문에 제약조건이 필요합니다.

  • new키워드를 사용할 수 없도록 private접근제어자를 사용해야합니다.
  • 유일한 단일 객체를 보장해야합니다.
  • 단일 객체의 참조할 정적 참조 변수가 필요합니다.
  • 단일 객체를 참조할 정적 메서드가 필요합니다.
public class SingletonService {
	//정적 참조 변수
	private static Singleton instance;
	
	//생성자 접근 제어
	private SingletonService(){}
	
	//단일 객체 보장을 위한 메서드 getInstance()
	public static SingletonService getInstance(){
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	
}			

결과 확인

@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest() {
    SingletonService instance1 = SingletonService.getInstance();
    SingletonService instance2 = SingletonService.getInstance();

    //참조값 확인
    System.out.println("instance1 = " + instance1);
    System.out.println("instance2 = " + instance2);


    //Test
    assertThat(instance1).isSameAs(instance2);
}

기본 구현 방법 문제점

멀티 스레드 환경에서 기본구현을 진행하였을때 Thread safe하지 않다는 문제점이 발생합니다.. A/B의 스레드가 있다고 하면 A스레드의 싱글톤 검증이 완료되지 않았을 때 B의 검증이 시작되어 2개의 싱글톤 인스턴스가 발생하는 문제가 생깁니다.





Thread safe initialization

synchronized를 사용하여 메서드에 스레드를 하나씩 접근하게 하는 방법입니다.
단점으로는 메서드를 계속해서 호출하여 동기화를 처리하기 때문에 오버헤드가 발생하여 성능저하 문제가 있습니다.

public class SingletonService {

	private static Singleton instance;
	
	private SingletonService(){}
	
	//synchronized 메서드
	public static synchronized Singleton getInstance(){
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;	
}			

💡 synchronized?
멀티스레드 환경에서 두개의 이상의 스레드가 하나의 변수/메서드에 접근을 제한하여 Race Condition발생을 방지하는 것입니다.
즉, 메서드가 동작 중이면 "접근금지"명령을 내려 접근을 제한하는 키워드 입니다.


Bill Pugh Solution -LazyHolder

싱글톤 패턴을 생성할때 권장되는 방법 중 하나입니다.
멀티스레드 환경과 지연로딩이 가능한 구현 기법이며, static메서드에서는 static멤버만 호출할 수 있는점과 내부 클래스는 외부 클래스가 로드될 때 초기화 되지 않고 getInstance가 호출되면 메모리에 로드되는 과정을 통해 멀티스레드 환경에서도 안전하게 지연초기화를 진행하는 방법입니다.

public class Singleton {
	
	private Singleton() {}
	
	//static 내부 클래스 사용
	// holder를 생성하여 클래스가 메모리에 로드되지 않고 getInstance호출시에 로딩되도록 설정
	private static class SingleInstanceHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	
	public static Singleton getInstance() {
		return SingletonHolder.INSTANCE	;
	}
}	

Enum

싱글톤 패턴을 생성할 때 권장하는 두번째 방법입니다.
Enum은 상수로 이루어져 있으며 한번만 초기화를 진행하여 Thread safe하며, 변수나 메서드 선언이 가능하며 이를 싱글톤 클래스로 이용 가능합니다.

Enum기반 싱글톤은 상속을 지원하지 않아 확장이 필요하지 않은 경우에 적합하며, 일반 싱글톤 클래스를 Enum으로 변경시 코드변경에 대한 부담이 있을 수 있습니다.

enum SingletonEnum{
	INSTANCE;

	public static SingletonEnum getInstance(){
		return INSTANCE;
	}
}		




마무리

해당 내용은 스프링을 공부하면서 스프링 컨테이너는 싱글톤을 보장한다는 점을 보고 자세하게 알아보자 하는 마음으로 정리해 보았습니다.

싱글톤 패턴은 객체를 효율적으로 관리하고 자원을 절약하는 데 큰 장점을 제공하지만, 잘못 사용할 경우 다양한 문제를 야기할 수 있음을 이번 정리를 통해 알 수 있었습니다. 특히, 스프링 컨테이너는 이러한 싱글톤 패턴의 한계를 극복하면서도 객체의 생명 주기를 효과적으로 관리하여 개발자의 부담을 줄이는 역할을 합니다.

정리하는 과정을 통해 싱글톤 패턴과 스프링 컨테이너가 어떻게 연관되어 있으며, 왜 스프링이 싱글톤 Bean 관리 방식을 채택했는지 이해할 수 있었습니다. 이를 기반으로 스프링 컨테이너의 동작 원리를 더 깊이 탐구해보는 기회를 가지게 되었습니다.



Reference

https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/
https://ittrue.tistory.com/563

profile
말하는 감자

0개의 댓글