-
[Spring] 스프링 핵심 원리 - ThreadLocalStudy/Spring 2025. 4. 19. 23:15
일반 변수와 ThreadLocal 변수 비교
1) 일반 변수 필드
- 여러 스레드가 같은 인스턴스의 필드(멤버 변수)에 접근하면 처음 스레드가 보관한 데이터가 사라질 수 있음
예시)
- thread-A가 userA라는 값을 저장하고, thread-B가 userB라는 값을 저장하면 직전에 thread-A가 저장한 userA 값은 덮어씌워짐
2) ThreadLocal로 선언된 필드
- ThreadLocal은 해당 스레드만 접근할 수 있는 특별한 저장소를 말함
- 즉, ThreadLocal을 사용하면 각 스레드마다 별도의 내부 저장소를 제공함. 따라서 같은 인스턴스의 스레드 로컬 필드에 접근해도 문제 없음
- 자바는 언어 차원에서 스레드 로컬을 지원하기 위한 java.lang.ThreadLocal 클래스를 제공함
예시)
- thread-A가 userA라는 값을 저장하면 스레드 로컬은 thread-A 전용 보관소에 데이터를 보관함
- thread-B가 userB라는 값을 저장하면 스레드 로컬은 thread-B 전용 보관소에 데이터를 보관함
- 스레드 로컬을 통해 데이터를 조회할 때도 thread-A가 조회하면 스레드 로컬은 thread-A 전용 보관소에서 userA 데이터를 반환해주고, thread-B가 조회하면 thread-B 전용 보관소에서 userB 데이터를 반환해줌
ThreadLocal 주의사항
- 스레드 로컬의 값을 사용 후 제거하지 않으면 WAS(톰캣)처럼 스레드 풀을 사용해 스레드를 재사용하는 경우 문제가 발생할 수 있음
- 다음 요청이 이전 요청의 데이터를 보게되는 문제
상황
더보기사용자A 저장 요청
- 사용자A가 저장 HTTP를 요청함
- WAS는 스레드 풀에서 스레드를 하나 조회함
- 해당 요청 처리를 위해 스레드 thread-A가 할당됨
- thread-A는 사용자A의 데이터를 스레드 로컬에 저장함
- 스레드 로컬의 thread-A 전용 보관소에 사용자A 데이터를 보관함
사용자A 저장 요청 완료
- 사용자A의 HTTP 응답이 반환되어 끝남
- WAS는 사용이 끝난 thread-A를 스레드 풀에 반환함 (스레드 풀을 통해 스레드를 재사용함)
- thread-A는 스레드 풀에 아직 살아있으므로, 스레드 로컬의 thread-A 전용 보관소에 사용자A 데이터도 아직 저장되어 있음
사용자B 조회 요청
- 사용자B가 조회를 위한 새로운 HTTP 요청함
- WAS 스레드 풀에서 스레드를 하나 조회함
- 스레드 thread-A가 할당됨 (물론 다른 스레드가 할당될 수도 있음)
- 이번에는 조회 요청이므로, thread-A는 스레드 로컬에서 데이터를 조회함
- 스레드 로컬은 thread-A 전용 보관소에 있는 사용자A 값을 반환함
- 결과적으로 사용자A 데이터가 반환됨
- 사용자B는 사용자A의 정보를 조회하게 됨
즉, 사용자B가 사용자A의 데이터를 보게되는 상황이 발생하게 됨
방안
- 요청이 끝날 때 스레드 로컬의 값을 ThreadLocal.remove()를 통해 꼭 제거해야 함
추가) 스레드 로컬 동작 이해
- ThreadLocal은 스레드마다 독립된 값을 저장할 수 있게 해주는 클래스
- 즉, 각 스레드가 자신만의 변수 복사본을 가지도록 해주는 메커니즘
- 멀티스레드 환경에서 공유되지 않는 상태를 유지해야할 때 유용함
- ex) 로그 트레이싱, 트랜잭션 ID 저장, 로그인 사용자 정보 유지 등
예제)
더보기public class Example { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { threadLocal.set("main-thread-value"); new Thread(() -> { threadLocal.set("child-thread-value"); System.out.println(threadLocal.get()); // child-thread-value }).start(); System.out.println(threadLocal.get()); // main-thread-value } }
- 각 스레드가 threadLocal.set()을 호출하면 해당 스레드만 접근 가능한 저장소에 값이 저장됨
- 다른 스레드에서는 해당 값이 보이지 않음
내부 동작 원리
- ThreadLocal은 값을 직접 저장하지 않음
- 각 Thread 인스턴스 내부에 ThreadLocalMap이라는 Map이 존재함
- 해당 Map의 key는 ThreadLocal 인스턴스 자신이고, value는 해당 값이 됨
Thread A └── ThreadLocalMap └── [ThreadLocal 인스턴스] → "A의 값" Thread B └── ThreadLocalMap └── [ThreadLocal 인스턴스] → "B의 값"
코드 살펴보기
1) ThreadLocal.set(T value) 호출했을 때
더보기// ThreadLocal class public void set(T value) { set(Thread.currentThread(), value); if (TRACE_VTHREAD_LOCALS) { dumpStackIfVirtualThread(); } } private void set(Thread t, T value) { ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } }
- 현재 실행중인 Thread.currentThread()에서 ThreadLocalMap을 가져옴
- 해당 ThreadLocal 인스턴스를 key로 설정하여 Map에 값 저장
2) ThreadLocal.get() 호출했을 때
더보기public T get() { return get(Thread.currentThread()); } private T get(Thread t) { 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(t); }
- 현재 스레드의 ThreadLocalMap에서 현재 ThreadLocal 인스턴스를 key로 가지는 값 반환
참고
'Study > Spring' 카테고리의 다른 글
[Spring] 스프링 핵심 원리 - 스프링이 지원하는 프록시 (0) 2025.04.20 [Spring] 스프링 핵심 원리 - 동적 프록시 (0) 2025.04.20 [Spring] Spring MVC - 필터, 인터셉터 (0) 2025.04.19 [Spring] Spring MVC - 구조 이해 및 기본 기능 (0) 2025.04.19 [Spring] Spring MVC - 서블릿 (0) 2025.04.17