같은 로직의 요청이 2개가 동시에 온다면 인스턴스를 하나만 갖고있는 싱글턴 컨테이너에서 어떻게 처리하는지 학습하고자 했다.
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 특성이 싱글턴빈에 왜 중요한지 알아보기 위해서 부작용을 살펴보자.
@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로 관리하는 것이 중요하다.
https://www.baeldung.com/spring-singleton-concurrent-requests