Spring/스프링 핵심 원리 - 기본편

[싱글톤 컨테이너] 싱글톤 방식의 주의점 - 무상태(stateless) 설계

alsruds 2023. 11. 30. 22:16
🧐강의🧐
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

· 싱글톤 객체는 상태를 유지(stateful)하도록 설계해서는 안됨 : 여러 클라이언트가 같은 객체를 공유하기 때문

➡️ 무상태(stateless)로 설계 !!

          》 특정 클라이언트에 의존적인 필드 X

          》 특정 클라이언트가 값을 변경할 수 있는 필드 X (가급적 읽기만)

          》 필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등 사용

 

☑️ 문제 상황

· 공유 필드의 값을 특정 클라이언트가 변경할 수 있을 때

// StatefulService
public class StatefulService {

    // 상태를 유지하는 필드
    private int price;

    public void order(String name, int price) {
        System.out.println("name = " + name + "price = " + price);

        // 문제가 되는 부분 !!
        this.price = price;
    }

    public int getPrice() {
        return price;
    }
}

// StatefulServiceTest
class StatefulServiceTest {

    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // Thread A : A 사용자 10000원 주문
        statefulService1.order("userA", 10000);

        // Thread B : B 사용자 20000원 주문
        statefulService2.order("userB", 20000);

        // Thread A : A 사용자 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println("A price = " + price);

        assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }

    static class TestConfig {
        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }
}

Thread A 사용자는 10000 원을 기대했지만, 20000 원 출력 !

 

➡️ 공유 필드인 StatefulService 의 price 필드의 값을, 특정 클라이언트가 값을 변경하면서 발생한 장애

 

해결 방법

· 스프링 빈은 항상 무상태(stateless)로 설계하기 !!

// StatefulService
public class StatefulService {

    // 상태를 유지하는 필드
    // private int price;

    public int order(String name, int price) {
        System.out.println("name = " + name + "price = " + price);

        // 문제가 되는 부분 !!
        // this.price = price;

        return price;
    }
    
//    public int getPrice() {
//        return price;
//    }
}

// StatefulServiceTest
class StatefulServiceTest {

    ...
    
    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // Thread A : A 사용자 10000원 주문
        int userAPrice = statefulService1.order("userA", 10000);

        // Thread B : B 사용자 20000원 주문
        int userBPrice = statefulService2.order("userB", 20000);

        // Thread A : A 사용자 주문 금액 조회
        // int price = statefulService1.getPrice();
        System.out.println("A price = " + userAPrice);

        // assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }
}

해결 😊