FrameWork/Spring&Spring-boot

Spring 기본 15 - 싱글톤 방식의 주의점

Surge100 2023. 11. 13. 09:25

싱글톤 방식의 주의점

  • 싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나마 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.
  • ⭐무상태(stateless)로 설계해야 한다.
    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
    • 가급적 읽기만 해야 한다.
    • 필드 대신에 자에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal등을 사용해야 한다.
  • 스프링 빈 필드에 공유 값을 설정하면 정말 ⛔큰 장애가 발생할 수 있다.

상태를 유지할 경우 발생하는 문제점 예

클라이언트 의도

주문한다음에 price에 값을 저장을 한다.

 

StatefulService.java

package com.hello.core.singleton;

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.java

class StatefulServiceTest {

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

        //1.TreadA: A사용자가 10000원 주문
        statefulService1.order("userA",10000);
        //2.TreadA: B사용자가 10000원 주문( A사용자가 금액을 주문하기 전에)
        statefulService2.order("userB",20000);

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

    static class TestConfig{

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

 

위 테스트코드를 실행하면 중간에서 사용자 B의 주문으로 price의 가격이 바뀌었기 때문에 price가 2000으로 출력되고 사용자A가 주문하려고 했던 가격 10000원은 사라진다.

 

각각 다른 statefulService1과 statefulService2를 사용한것 같기만 사실  tatefulService1과 statefulService2 은 싱글톤 객체 인스턴스로 같은 인스턴스이다.

 

  • 최대한 단순히 설명하기 위해, 실제 쓰레드는 사용하지 않았다.
  • ThreadA가 사용자A 코드를 호출하고 ThreadB가 사용자B 코드를 호출한다고 가정하자
  • StateService의 price 필드는 공유되는 필드인데 특정 클라이런트가 값을 변경한다.
  • 사용자A의 준무금액은 10000원이 되어야 하는데, 20000원이라는 결과가 나왔다.
  • 실무에서 이런 경우는 종종 보이는데, 이로인해 정말 해결하기 어려운 큰 문제들이 발생한다.
  • 진짜 ⭐공유필드는 조심해야 한다. 스프링 빈은 항상 무상태(stateless)로 설계하자.
모 포털에 서 발생했던 문제
-> 나의 아이디로 로그인 했는데, 다른 사람의 이름이 보이는
-> 나의 결제 내역에 들어 갔는 다른 사람의 결제내역이 보이는것

 

 

[출처 - 스프링 핵심 원리 - 기본편]