ThreadLocal
쓰레드 로컬은 해당 쓰레드만 접근할 수 있는 특별한 저장소를 말함. 간단한 예로 들면 물건 보관 창구와 비슷함. 여러 사함이 같은 물건 보관 창구를 사용하더라도 창구 직원은 사용자를 인식하여 사용자별로 확실히 물건을 구분해 줌. 즉, 사용자A, 사용자B 모두 창구 직원을 통하여 물건을 보관하고 꺼내지만 창구 직원이 사용자에 따라 보관한 물건을 구분해 주는 식.
일반적인 변수 필드 여러 쓰레드가 같은 인스턴스의 필드에 접근하면 처음 쓰레드가 보관한 데이터가 사라질 수 있음.
- thread-A가 userA라는 값을 저장
- thread-B가 userB라는 값을 저장하면 직전에 thread-A가 저장한 userA라는 값이 사라짐
쓰레드 로컬 쓰레드 로컬을 사용하면 각 쓰레드마다 별도의 내부 저장소를 제공하여, 같은 인스턴스의 쓰레드 로컬 필드에 접근해도 데이터에 대한 동시성 문제가 발생하지 않음
- thread-A가 userA라는 값을 저장하면 쓰레드 로컬은 thread-A 전용 저장소에 데이터 저장
- thread-B가 userB라는 값을 저장하면 쓰레드 로컬은 thread-A 전용 저장소에 데이터 저장
- 쓰레드 로컬을 통해 데이터를 조회할 떄도, thread-A가 조회하면 thread-A 전용 보관소에 있는 데이터를 반환하고, thread-B가 조회하면 thread-B 전용 보관소에 있는 데이터를 반환 함.
자바는 언어 차원에서 쓰레드 로컬을 지원하는 클래스를 제공 함.
- java.lang.ThreadLocal
ThreadLocal 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
public class ThreadLocalService {
private ThreadLocal<String> nameStore = new ThreadLocal<>();
public void save(String name) {
log.info("저장 name={} -> nameStore={}", name, nameStore.get());
nameStore.set(name);
}
public String load() {
log.info("조회 nameStore={}",nameStore.get());
return nameStore.get();
}
public void remove() {
log.info("제거 nameStore={}",nameStore.get());
return nameStore.remove();
}
}
- 값 저장
- ThreadLocal.set(…)
- 값 조회
- ThreadLocal.get()
- 값 제거
- ThreadLocal.remove()
해당 쓰레드가 쓰레드 로컬을 모두 사용하고 나면 ThreadLocal.remove()를 호출하여 쓰레드 로컬에 저장된 값을 제거해 주어야 함.
ThreadLocal 주의사항
쓰레드 로컬의 값을 사용후 제거하지 않고 그냥 두게 되면, WAS(톰캣)처럼 쓰레드 풀을 사용하는 경우 심각한 문제가 발생 할 수 있음.
사용자 A 저장 요청
- 사용자A가 저장 HTTP 요청
- WAS는 쓰레드 풀에서 쓰레드를 하나 조회
- 쓰레드 Thread-A 할당
- Thread-A는 사용자A의 데이터를 쓰레드 로컬에 저장
- 쓰레드 로컬의 Thread-A 전용 보관소에 사용자A 데이터 보관
사용자 A 저장 요청 종료
- 사용자A의 HTTP 응답 종료
- WAS는 사용이 끝난 Thread-A를 쓰레드 풀에 반환
- 쓰레드를 생성하는 비용은 비싸기 때문에 쓰레드를 제거하지 않고 보통 쓰레드 풀을 통해 쓰레드를 재사용 함
- Thread-A는 쓰레드풀에 존재하ㄴ므로 Thread-A는 소멸되지 않은 상태이므로, 쓰레드 로컬의 Thread-A 전용 보관소에는 아직 사용자A의 데이터가 남아있게 됨
사용자 B 조회 요청
- 사용자B가 조회를 위한 새로운 HTTP 요청
- WAS는 쓰레드 풀에서 쓰레드를 하나 조회
- 쓰레드 Thread-A가 할당
- 쓰레드 풀의 쓰레트 할당 전략에 따라 다른 쓰레드가 할당 될수도 있음
- 조회하는 요청이므로 Thread-A는 쓰레드 로컬에서 데이터를 조회
- 쓰레드 로컬은 Thread-A 전용 보관소에 있는 사용자A 값을 반환
- 결과적으로 사용자B에게 사용자A의 값이 반환됨
- 사용자B는 사용자A의 정보를 조회하게 됨
결과적으로 사용자B는 사용자A의 데이터를 확인하게 되는 심각한 문제가 발생 할 수 있음. 그러므로 사용자A의 요청이 끝날때 쓰레드 로컬의 값을 ThreadLocal.remove()를 통해 반드시 제거해야 함.
참고
- 스프링 핵심 원리 - 고급편(김영한)