싱글톤 패턴이란?
싱글톤 패턴(Singleton Pattern)은 디자인 패턴 중 생성 패턴으로 하나의 클래스에 오직 하나의 인스턴스만 가지도록 하는 패턴이다. 하나의 클래스를 기반으로 여러가지 인스턴스를 만들 수 있지만, 그렇게 하지 않고 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만든다.
<싱글톤 그림>
싱글톤 패턴 구현
public class Singleton {
private static Singleton singletonInstance;
// 생성자를 외부에서 접근하지 못하도록 private 접근제어
private Singleton() {}
public static Singleton getInstance() {
if (singletonInstance == null) {
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
싱글톤 패턴을 구현할 때, singletonInstance 변수와 getInstance() 메소드를 정적 변수와 메소드로 선언하는 이유는
- 클래스 레벨에서 단 하나의 인스턴스를 유지하기 위해서
- 힙 메모리가 아닌 클래스영역에 저장되어서 전역으로 접근할 수 있게 하기 위해서
정적 메소드로 구현하지 않았을 경우
public class NotSingleton {
private NotSingleton() {
}
public NotSingleton getInstance() {
return new NotSingleton(); // 새로운 인스턴스를 생성
}
public static void main(String[] args) {
NotSingleton instance1 = new NotSingleton().getInstance();
NotSingleton instance2 = new NotSingleton().getInstance();
}
인스턴스 유무를 조회할 때 마다 새로운 다른 객체를 생성해야 하므로 정적 변수와 메소드를 사용해야 하나의 인스턴스를 유지할 수 있다.
여러 스레드에서 접근시 문제 발생 및 해결
싱글톤 패턴의 경우 단일 객체이기 때문에, 상태값을 갖지 않는 것이 좋다. 단일 객체가 상태 값을 가지는 경우 특정 참조 변수가 상태를 변경했을 때 다른 참조 변수에도 영향을 미치기 때문이다. 멀티스레드 환경에서 싱글톤의 상태값이 존재하지 않기 때문에 객체 생성 유무 판단에서 동기화가 되지 않을 수도 있다.
public class Singleton {
private static Singleton singletonInstance;
private Singleton() {}
public static Singleton getInstance() { // 두 번째 Thread 진입 가능
if (singletonInstance == null) { // 첫 번째 Thread 진입
singletonInsatance = new Singleton();
}
return singletonInstance;
}
}
그렇기 때문에 여러가지 방법으로 이 문제를 해결한다.
- Synchronized 사용
public class Singleton {
private static Singleton singletonInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singletonInstance == null) {
singletonInsatance = new Singleton();
}
return singletonInstance;
}
}
synchronized를 사용할 경우 하나의 스레드만 접근이 가능하다.
- final 변수 이용
public class Singleton {
private final static Singleton singletonInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singletonInstance;
}
}
변수를 선언하면서 동시에 인스턴스를 생성할 수 있다. 하지만 이 객체를 사용하지 않을 경우 메모리 낭비로 이어질 수 있다.
- Bill Pugh solution 사용
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton SINGLETON = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.SINGLETON_OBJECT;
}
}
싱글톤 패턴의 단점
싱글톤 패턴은 TDD(Test Driven Development)를 할 때 걸림돌이 된다. TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 한다.
스프링에서 싱글톤 패턴을 어떻게 적용하고 있을까?
스프링 컨테이너는 싱글톤 객체를 직접 생성하고, 내부에서 캐싱하여 재사용한다. @Configuration, @Component, @Bean 등의 어노테이션을 활용하여 스프링 내에서 사용하고 싶은 객체들을 Bean을 통해 생명주기를 관리한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
여기서 @Configuration을 통해 AppConfig를 스프링 설정정보로 정의한다. 이 안에 들어있는 @Bean 이 붙어있는 메소드 내의 리턴 객체들을 스프링 컨테이너에 Bean으로 등록하여 스프링에서 사용한다.
그럼 대체 이 Bean을 어떻게 싱글톤을 통해서 관리한다는 걸까?
스프링 내부에서 동작하는 싱글톤 레지스트리
위 그림과 같이 만약 싱글톤이 작동하지 않는다면 스프링 컨테이너에서 사용자가 서비스를 요청할 때 마다 서비스 객체가 생성 될 것이다. 이럴 경우 같은 객체를 생성하는데 너무 많은 양의 데이터가 낭비된다. 그렇기 때문에 동일한 요청을 하는 경우에는 하나의 인스턴스만 생성하는게 좋다.
그래서 스프링 내에서는 DefaultSingletonBeanRegistry라는 곳에서 아래 코드처럼 동작을 하며 싱글톤을 적용한다.
Object sharedInstance = singletonObjects.get(beanName);
if (sharedInstance == null) {
sharedInstance = createBean(beanName);
singletonObjects.put(beanName, sharedInstance);
}
return sharedInstance;
DefaultSingletonBeanRegistry는 AbstractBeanFactory의 부모 클래스이다. 그래서 모든 스프링 Bean의 생성과 관리는 이 클래스에서 이루어진다.
@Configuration 테스트 코드
@Configuration을 달지 않게 되면 @Bean이 적혀있는 메소드들이 일반 메소드 취급을 받게되면서 객체가 싱글톤으로 적용되지 않는다. 그래서 @Configuration을 주석처리하고 빈이 생성이 되었는지 확인해보았다.
AppConfig 클래스를 applicationContext로 지정하고 생성한 memberService 빈에서 가져온 Repository 객체와 따로 생성한 memberRepository 객체가 같은지를 비교해보는 테스트이다.
그 결과 두 객체의 주소값은 다르게 나왔다.
@Configuration을 붙였을 때,
결론은 @Configuration을 설정하고 @Bean으로 객체들을 등록했을 때 싱글톤으로 스프링이 알아서 관리해준다는 것을 알았다.
'JAVA' 카테고리의 다른 글
[JAVA] 전략 패턴(Strategy Pattern)이란? (1) | 2025.05.23 |
---|---|
[JAVA] 팩토리 패턴(Factory Pattern)이란? (0) | 2025.05.22 |
[JAVA] 예외클래스와 예외처리 (2) | 2024.12.08 |
[JAVA] Heap 메모리 구조와 Garbage Collection (2) | 2024.12.08 |
[JAVA] 중첩클래스, 중첩인터페이스 (1) | 2024.12.03 |