싱글톤 패턴과 무상태 (stateless) 설계

sangcheol·2023년 4월 2일

싱글톤 패턴

싱글톤 패턴은 전체 응용프로그램에서 클래스의 인스턴스를 하나만 만들고 그 인스턴스를 모두가 공유해서 접근할 수 있도록 소프트웨어 개발에 널리 사용되는 디자인 패턴이다.

싱글톤 패턴에서는 클래스의 단일 인스턴스가 생성되고 해당 클래스에 대한 모든 후속 요청은 동일한 인스턴스를 반환한다.

상태(Stateful) vs 무상태(Stateless)

상태(Stateful)

싱글톤을 설계할 때 이를 구현하는 방법은 크게 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씩 증가하게 된다.

상태(Stateful) 설계의 문제점

간단한 테스트 코드를 통해 상태 설계의 문제를 알아보자.

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)

이러한 문제를 방지하기 위해서는 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)로 설계 해야함을 기억하자.

profile
백엔드 개발자

0개의 댓글