[Spring] 싱글톤, 컨테이너

노유성·2023년 7월 13일
0
post-thumbnail

싱글톤이란

클래스의 instance가 1개만 생성되는 것을 보장하는 디자인 패턴이다.

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {
        
    }
    
    public void logic() {
        System.out.println("싱글톤 객체 로직 호출");
    }
}

다음과 같은 방식으로 정적 필드에 instance 1개를 넣어놓고 instance를 사용하기 위해서는 클라이언트에서 getInstance()를 호출하도록 규명하고있다. 또한, 생성자의 제어자를 private으로 두어 생성자로 instance를 생성하는 것을 막는다.

Singleton을 사용하지 않는 경우


싱글톤 패턴을 사용하지 않는 경우 client가 instance를 요청할 때마다 새로운 instance를 생성하고 삭제한다. 이러면 자원이 너무 많이 든다.

Singleton을 사용하는 경우

Singleton의 문제점

이렇게 instance를 공유자원으로 두는 싱글톤 패턴에도 문제점이 있다.

  • 구현을 위한 코드가 많이 들어간다.
  • client가 구체에 의존한다.
  • OCP를 위반할 가능성이 높다.
  • 유연성이 떨어진다.

Singleton의 주의점

객체를 하나만 생성해서 공유하는 싱글톤 방식은 여러 client가 하나의 instance를 공유하기 때문에 stateful하게 설계해서는 안 된다.
즉, 무상태로 설계해야한다. 무상태로 설계한다는 것의 의미는 다음과 같다.

  • client에 의존적인 필드가 없어야한다.
  • client가 값을 변경할 수 있는 필드가 존재해서는 안 된다.
  • 가급적이면 읽기만 가능해야 한다.
  • java에서 공유되지 않는 local, parameter, ThreadLocal을 사용해야 한다.

Race condition 예제

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를 저장하는 service이다.

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

        //ThreadA: 10000원 주문
        statefulService1.order("userA", 10000);

        //ThreadB: 20000원 주문
        statefulService2.order("userA", 20000);

        //ThreadA: 10000원 주문
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);
    }

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

이를 Bean으로 이용하는 컨테이너를 사용해서 A사용자가 10000원 B사용자가 20000원을 주문한 상황이다. B사용자가 주문을 하면은 등록된 bean의 price 필드가 갱신되므로 뒤늦게 A사용자가 가격을 조회하려고 해도 이미 이 값이 사라져있으므로 race condition이 발생한 것이다.

이와 같은 문제를 방지하기 위해서 싱글톤 패턴에는 client가 변경 가능한 필드를 두어서는 안 된다.

profile
풀스택개발자가되고싶습니다:)

0개의 댓글