싱글톤 패턴은 전체 응용프로그램에서 클래스의 인스턴스를 하나만 만들고 그 인스턴스를 모두가 공유해서 접근할 수 있도록 소프트웨어 개발에 널리 사용되는 디자인 패턴이다.
싱글톤 패턴에서는 클래스의 단일 인스턴스가 생성되고 해당 클래스에 대한 모든 후속 요청은 동일한 인스턴스를 반환한다.
싱글톤을 설계할 때 이를 구현하는 방법은 크게 Stateful한 설계 방식과 Stateless 설계 방식으로 나눌 수 있다. stateful 싱글톤은 변경 가능한 상태를 가진 싱글톤이고, stateless 싱글톤은 변경 가능한 상태가 없는 싱글톤을 말한다.
Java의 예제 코드를 통해 stateful 설계에 대해 자세히 알아보자.
public class MyStatefulSingleton {
private static MyStatefulSingleton instance;
private int count; // 상태 유지 필드
private MyStatefulSingleton() {}
public static MyStatefulSingleton getInstance() {
if (instance == null) {
instance = new MyStatefulSingleton();
}
return instance;
}
public void incrementCount() {
count++; // 문제가 될 수 있는 부분
}
public int getCount() {
return count;
}
}
위의 예제에서 MyStatefulSingleton은 변경 가능한 count 필드가 있는 상태 저장 싱글톤이다. incrementCount() 메서드가 호출될 때마다 count 값이 1씩 증가하게 된다.
간단한 테스트 코드를 통해 상태 설계의 문제를 알아보자.
import org.assertj.core.api.Assertions;
import org.junit.Test;
public class SingletonTest {
@Test
public void testStatefulSingleton() throws InterruptedException {
MyStatefulSingleton s1 = MyStatefulSingleton.getInstance();
MyStatefulSingleton s2 = MyStatefulSingleton.getInstance();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
s1.incrementCount();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
s2.incrementCount();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
int count = s1.getCount();
System.out.println("Count: " + count);
}
}
두 개의 스레드를 만들고 count를 1000000번 증가시키는 루프를 만들어 스레드를 실행시키면, count 값이 2000000이 될꺼라는 예측과 다른 결과가 나온다는 것을 알 수 있다.
MyStatefulSingleton과 같은 상태 저장 싱글톤을 갖는 것은 보기에는 편리해 보일 수 있지만 다중 스레드 환경에서 아주 커다란 문제를 일으킬 수 있다. 여러 스레드가 MyStatefulSingleton의 동일한 인스턴스에 동시에 접근하게 되면 예측할 수 없는 동작으로 이어지는 심각한 문제가 발생할 수 있다.
이러한 문제를 방지하기 위해서는 Stateless 싱글톤 방식으로 설계하는 것이 좋다.
다음의 Java 예제를 살펴보자.
public class MyStatelessSingleton {
private static final MyStatelessSingleton INSTANCE = new MyStatelessSingleton();
private MyStatelessSingleton() {}
public static MyStatelessSingleton getInstance() {
return INSTANCE;
}
...
}
위의 예제 코드에서 MyStatelessSingleton은 변경 가능한 상태가 없는 무상태 싱글톤이다. 변경 가능한 상태가 없기 때문에 동시성 문제에 대한 걱정이 없이 여러 스레드에서 안전하게 엑세스할 수 있다.
싱글톤을 stateless 방식으로 설계하는 것은 동시성 문제를 피하고 코드를 더 예측 가능하고 유지 관리할 수 있도록 만드는 데 도움이 된다. stateful 싱글톤 방식이 편리해 보일 수 있지만 다중 스레드 환경에서 찾아내기 힘든 아주 큰 문제를 일으킬 수 있다.
java spring 프레임워크에서 스프링 컨테이너는 기본적으로 싱글톤 컨테이너이다. 스프링 빈은 항상 무상태(stateless)로 설계 해야함을 기억하자.