jhhan의 블로그

스프링 코어(6) - 싱글톤(Singleton) 주의점 본문

Spring

스프링 코어(6) - 싱글톤(Singleton) 주의점

jhhan000 2021. 4. 27. 13:42

이번 포스트에서는 싱글톤 방식에서 주의해야할 점에 대해서 알아보겠습니다.

 

싱글톤 패턴이나 스프링의 싱글톤 컨터이너이거나

객체 인스턴스를 하나만 생성해서 공유하는 방식에서 주의해야할 점이 있습니다.

바로 상태를 유지하게(stateful) 설계하면 안된다는 것입니다.

상태가 없도록(stateless) 설계해야 합니다.

 

즉,

  • 특정 클라이언트에서 의존적인 필드가 존재하면 안됨.
  • 특정 클라이언트에서 값을 변경할 수 있으면 안됨.
  • 수정 로직이 있으면 안됨 & 읽기 로직만 존재할 것
  • 공유되지 않는 지역변수, 파라미터 등을 사용해서 진행해야 함

이렇게 하지 않고 스프링 빈의 필드에 공유 값을 설정하면 큰 문제가 발생할 수 있습니다.

 

문제가 일어나는 예시를 한번 살펴보겠습니다.

singleton 패키지 밑에 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;
    }
}

이 예시에서 price는 상태를 유지해야하는 필드로

공유되면 문제가 생깁니다.

 

singleton 패키지 밑에 StatefulServiceTest라는 자바 파일을 생성해서

테스트 코드를 작성해봅니다.

import static org.junit.jupiter.api.Assertions.*;

class StatefulServiceTest {

    /**
     *  StatefulService의 price 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경함...
     *  사용자 A의 주문금액이 10000원인데 20000원이라는 결과가 나옴.. ㄷㄷㄷ
     *  실제로 이런 문제가 일어나면 어마어마한 문제들이 터지게 됨.
     */

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

        // ThreadA : 사용자 A가 10000원을 주문
        statefulService1.order("userA", 10000);
        // ThreadB : 사용자 B가 20000원을 주문
        statefulService2.order("userB", 20000);

        // ThreadA - 사용자 A의 주문 금액 조회
        int price1 = statefulService1.getPrice();
        System.out.println("price1 = " + price1);

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

        // 단순한 예제를 위해서 실제 멀티쓰레드는 사용하지 않음...
    }

    static class TestConfig {

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

이 코드는 예시를 위한 코드입니다.

실제 코드는 멀티 쓰레드를 사용하는 등 훨씬 복잡하게 작동한다고 합니다.

  • 사용자 A는 10000원을 주문했고, 사용자 B는 20000원을 주문함.
  • 이 때 사용자 A의 금액을 조회하기 위한 코드 작성
  • 가격은 얼마가 나올까?

다들 눈치채셨겠지만, price1의 가격은 10000이 아닌 20000이 나오게 됩니다.

price라는 필드는 공유되는 필드인데, 특정 클라이언트가 값을 수정하는 일이 있어서

이런 오류가 발생하는 것입니다.

 

실제로 이런 일이 일어났다면 꽤나 끔찍할 것입니다...

 

그렇기 때문에 공유되는 필드는 항상 stateless인 상태로 관리가 되어야 합니다!!

 

 

 

정말 간단하게 알아본 싱글톤 패턴의 문제점 & 주의점입니다.

코드 작성할 때 항상 이 변수 & 필드가 어떻게 사용되는지 생각하고 작성해야 할 것 같습니다 ㅎㅎ

 

 

이렇게 포스트를 마치겠습니다.

 

 

 

출처: 인프런 - 스프링 핵심원리(기본편) by 김영한