스프링의 싱글턴 빈은 어떻게 동시성 요청을 처리할 수 있는가?

Hyunta·2022년 11월 15일
0
post-custom-banner

학습동기

같은 로직의 요청이 2개가 동시에 온다면 인스턴스를 하나만 갖고있는 싱글턴 컨테이너에서 어떻게 처리하는지 학습하고자 했다.

Java의 Heap 영역

Java의 힙영역은 어떤 쓰레드에서든지 전역적으로 접근할 수 있다. Spring 컨테이너가 싱글턴 스코프로 빈을 생성하면 힙 영역에 저장된다. 이런 방법으로 처리하면 동시에 요청온 모든 쓰레드들이 같은 인스턴스에 접근할 수 있다. 이어서 쓰레드의 Stack 영역을 살펴보면서 어떻게 동시성 요청을 도울 수 있는지 알아보자

어떻게 동시성 요청이 전달되는가?

@Service
public class ProductService {
    private final static List<Product> productRepository = asList(
      new Product(1, "Product 1", new Stock(100)),
      new Product(2, "Product 2", new Stock(50))
    );

    public Optional<Product> getProductById(int id) {
        Optional<Product> product = productRepository.stream()
          .filter(p -> p.getId() == id)
          .findFirst();
        String productName = product.map(Product::getName)
          .orElse(null);

        System.out.printf("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().getName(), this, id, productName);

        return product;
    }
}

해당 클래스가 싱글턴 빈으로 관리되고 있다고 생각하자.
동시에 요청이 들어온다고 생각하고 요청은 각각 1번과 2번 아이디를 요청한다.

스프링은 요청마다 다른 쓰레드를 생성하고, console 창에 아래와 같이 나온다.

Thread: pool-2-thread-1; bean instance: com.baeldung.concurrentrequest.ProductService@18333b93; product id: 1 has the name: Product 1
Thread: pool-2-thread-2; bean instance: com.baeldung.concurrentrequest.ProductService@18333b93; product id: 2 has the name: Product 2

스프링이 같은 빈을 여러 쓰레드에서 사용할 수 있는 이유는

첫째로, Java가 가 각 쓰레드마다 private stack 메모리를 만들기 때문이다.

stack 메모리는 쓰레드가 실행되는 메서드 내부에서 사용되는 local variable을 저장해야 한다. 따라서 병렬적으로 실행되는 쓰레드가 서로의 variable을 덮어쓰지 않을 수 있다.

둘째로, ProductService 빈이 heap level의 제한이나 락을 걸지 않기 때문이다. 각 쓰레드의 프로그램 카운터가 heap 메모리에 있는 같은 인스턴스를 바라볼 수 있다. 그러므로 여러개의 쓰레드가 같은 메서드를 동시에 실행시킬 수 있다.

이어서 싱글턴 빈이 왜 stateless한게 중요한지 알아보자.

Stateless Singleton Beans vs Stateful Singleton Beans

stateless 특성이 싱글턴빈에 왜 중요한지 알아보기 위해서 부작용을 살펴보자.

@Service
public class ProductService {
    private String productName = null;
    
    // ...

    public Optional getProductById(int id) {
        // ...

        productName = product.map(Product::getName).orElse(null);

       // ...
    }
}
Thread: pool-2-thread-2; bean instance: com.baeldung.concurrentrequest.ProductService@7352a12e; product id: 2 has the name: Product 2
Thread: pool-2-thread-1; bean instance: com.baeldung.concurrentrequest.ProductService@7352a12e; product id: 1 has the name: Product 2

1번과 2번으로 쓰레드에 요청을 보냈는데 둘다 Product 2번을 가져오는 에러가 발생했다.

원치않는 부작용을 방지하기 위해서, 싱글턴 빈을 stateless로 관리하는 것은 중요하다.

정리

Java의 힙 영역에서 관리되어지기 때문에 동시에 여러 쓰레드에서 같은 인스턴스에 접근할 수 있다. 여러 쓰레드가 인스턴스의 상태를 조작하게 되면 원치않는 결과를 얻을 수 있기 때문에 싱글턴빈은 stateless로 관리하는 것이 중요하다.

Reference

https://www.baeldung.com/spring-singleton-concurrent-requests

profile
세상을 아름답게!
post-custom-banner

0개의 댓글