스프링에서 빈은 싱글톤으로 등록된다.
즉 스프링 컨테이너에 딱 하나의 인스턴스가 생성된다는 의미이다.
이렇게 싱글톤인 인스턴스를 여러 쓰레드가 동시에 접근하면 동시성 문제가 발생한다.
동시성 문제
여러 쓰레드가 동시에 똑같은 인스턴스의 필드 값을 변경할 때 생기는 문제를 뜻한다.
자바는 이러한 동시성 처리를 위해 ThreadLocal 이라는 클래스를 제공한다.
ThreadLocal
해당 쓰레드만 접근할 수 있는 저장소로 각 쓰레드가 독립적으로 변수의 값을 유지할 수 있도록 한다.
멀티스레드 환경에서 쓰레드별로 상태를 저장할 때 사용된다.
즉 하나의 필드에 대해 여러 쓰레드가 접근하더라도 쓰레드마다 독립적인 값을 가져 동시성 처리에 안전하다.
이렇게 할 수 있는 이유는 ThreadLocal 내부에 Thread를 Key로 하고 저장하려는 필드를 Value로 하는 Map과 유사한 구조인 ThreadLocalMap을 가지고 있기 때문이다.
public class ThreadLocal<T> {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
...
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
...
}
사용하려면 다음과 같이 할 수 있다.
private ThreadLocal<String> store = new ThreadLocal<>();
public String logic(String value) {
store.set(value);
String storeValue = store.get();
}
정리하면 아래와 같이 동작한다.
- 클라이언트가 웹 애플리케이션에 HTTP 요청
- WAS(Web Application Server)가 요청을 받고 스레드 풀에서 스레드 할당
- DispatcherServlet이 요청을 받아 컨트롤러로 라우팅하고 이후 비즈니스 로직 수행
- 빈에 ThreadLocal이 있다면 저장된 정보를 저장하거나 조회
- 요청이 끝나면 ThreadLocal에 저장된 정보 클리어 (메모리 누수 방지)
- 처리된 스레드를 스레드 풀에 반환
여기서 주의할 점으로는 스레드 풀을 사용하면 스레드가 요청이 오면 할당되고 끝나면 풀에 반환된다.
해당 스레드가 계속 살아있는 상태이므로 ThreadLocal 내부의 데이터는 요청이 끝나도 정리되지 않고 계속 남아있는다.
다른 요청이 들어왔을 때 키가 같은 스레드가 할당되면 기존 데이터가 남아있는 문제가 발생할 수 있으므로 요청이 끝나면 ThreadLocal.remove() 메서드 호출을 꼭 해야한다.
ThreadLocal 데이터 제거는 스프링의 필터나 인터셉터에서 주로 처리한다.
참고
스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런
김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기
www.inflearn.com
'Spring' 카테고리의 다른 글
[Spring AOP] 프록시 팩토리 (0) | 2024.12.28 |
---|---|
[Spring AOP] 동적 프록시 (0) | 2024.12.28 |
[Spring] @Transactional (0) | 2024.12.27 |
[Spring] JDBC, 커넥션 풀, 트랜잭션 추상화 (0) | 2024.12.27 |
[Spring] MultipartFile 바인딩 (0) | 2024.12.27 |