Spring에서 여러 스레드에서 동일한 빈 인스턴스를 사용할 수 있는 이유
Spring이 여러 스레드에서 동일한 빈 인스턴스를 사용할 수 있는 이유는 각 스레드에 대해 Java가 개인 스택 메모리를 생성하기 때문입니다. 즉, 멤버 변수를 사용하지 않고 지역 변수만 사용하면, 각 요청은 독립적인 데이터 공간을 가지므로 동시성 문제가 발생하지 않습니다.
- 각 스레드에 대해 Java가 개인 스택 메모리를 생성하기 때문입니다.
- 스택 메모리는 스레드 실행 중 메서드 내부에서 사용되는 로컬 변수의 상태를 저장하는 역할을 합니다.
- 이런 방식으로 Java는 병렬로 실행되는 스레드가 서로의 변수를 덮어쓰지 않도록 합니다.
- Spring의 싱글턴 빈은 힙 메모리에 생성되지만, 동기화나 개별 스레드 제한 없이 공유되므로 여러 스레드가 동일한 인스턴스를 참조할 수 있습니다.
- 각 스레드는 동일한 싱글턴 빈의 인스턴스를 공유하더라도, 지역 변수를 사용하면 충돌 없이 독립적인 처리가 가능합니다.
1. 싱글턴 패턴의 동시성 문제
1.1. 상태를 가지는 싱글턴 빈의 문제
싱글턴 객체는 여러 요청에서 공유되므로, 내부 상태를 변경하는 멤버 변수가 있다면 동시성 문제가 발생할 수 있습니다.
예를 들어, 다음과 같은 코드가 있다고 가정해봅시다.
@Service
public class StatefulService {
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
이 클래스를 여러 요청이 동시에 사용할 경우:
@Service
public class OrderService {
private final StatefulService statefulService;
public OrderService(StatefulService statefulService) {
this.statefulService = statefulService;
}
public void order(int price) {
statefulService.setValue(price);
System.out.println("Price: " + statefulService.getValue());
}
}
- A 사용자가 order(10000)을 호출하면 value = 10000이 저장됨
- 동시에 B 사용자가 order(20000)을 호출하면 value = 20000으로 변경됨
- A가 getValue()를 호출하면 기대와 달리 20000이 반환될 가능성이 있음
이처럼 싱글턴 빈이 멤버 변수를 가지고 상태를 유지하면, 여러 스레드가 동시에 접근할 때 예상치 못한 결과가 발생할 수 있습니다.
2. Spring에서 싱글턴 패턴의 동시성 문제 해결 방법
2.1. 무상태(Stateless) 설계 (가장 권장)
Spring에서는 싱글턴 빈을 무상태(Stateless)로 설계하는 것이 가장 안전한 방법입니다. 즉, 멤버 변수를 사용하지 않고 지역 변수만 사용하면 동시성 문제가 발생하지 않습니다.
@Service
public class StatelessService {
public int calculateTotal(int price, int quantity) {
return price * quantity; // 상태를 유지하지 않음 (안전)
}
}
Spring 컨테이너에서는 위와 같은 방식으로 설계된 빈을 공유해도 동시성 문제가 발생하지 않습니다.
✅ 장점:
- 동시 요청이 와도 충돌이 없음 (스레드 안전)
- 관리가 쉬움
❌ 단점:
- 요청별로 상태를 저장해야 하는 경우 별도의 전략이 필요함
2.2. ThreadLocal 사용 (요청별 상태 유지 필요할 때)
Spring에서는 ThreadLocal을 활용하여 각 요청별 상태를 분리할 수 있습니다.
@Service
public class ThreadLocalService {
private static final ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
public void setValue(int value) {
threadLocalValue.set(value);
}
public int getValue() {
return threadLocalValue.get();
}
public void clear() {
threadLocalValue.remove();
}
}
Spring의 트랜잭션 컨텍스트나 HTTP 요청별 사용자 정보 저장 등에 ThreadLocal이 자주 사용됩니다.
✅ 장점:
- 요청별로 독립적인 값을 저장할 수 있음
❌ 단점:
- remove()를 호출하지 않으면 메모리 누수가 발생할 수 있음
- 요청이 끝난 후 반드시 clear()를 호출해야 함
쓰레드 로컬에 관련하여 제가 예전에 작성한 블로그 글입니다.
2022.11.05 - [프레임워크/스프링] - [Spring] 동시성 문제(쓰레드 로컬)
[Spring] 동시성 문제(쓰레드 로컬)
동시성 문제는 지역 변수에서는 발생하지 않는다. 지역 변수는 쓰레드마다 각각 다른 메모리 영역이 할당된다. > 동시성 문제가 발생하는 곳은 같은 인스턴스의 필드(주로 싱글톤에서 자주 발생
soso-shs.tistory.com
3. 결론
Spring의 싱글턴 패턴은 무상태(Stateless) 설계가 가장 안전하며, 만약 상태를 유지해야 한다면 ThreadLocal을 활용하여 동시성 문제를 방지할 수 있습니다. Spring 컨테이너에서 자동으로 관리하는 싱글턴 빈을 사용할 때는 멤버 변수를 피하고 요청별 데이터를 지역 변수 또는 요청 컨텍스트에 저장하는 것이 중요합니다.
참조: https://www.baeldung.com/spring-singleton-concurrent-requests
'프레임워크 > 스프링' 카테고리의 다른 글
Spring Scheduler와 Spring Quartz 차이 (0) | 2024.04.13 |
---|---|
Mockito와 BDDMockito (0) | 2024.03.20 |
[Spring] MockMvc (0) | 2024.03.18 |
[Spring] 프로메테우스, 그라파나 아키텍처 사용 (0) | 2024.02.16 |
[Spring] actuator (0) | 2024.02.15 |