ThreadLocal은 각 스레드(Thread)마다 독립적인 변수 저장소를 제공하는 Java 클래스이다.
즉, 같은 ThreadLocal 객체를 공유하더라도 스레드별로 서로 다른 값을 가진다.
ThreadLocal에 대해 자기만의 값을 가진다.ThreadLocal.set(value)를 호출하면,결론: ThreadLocal은 “공유 변수”처럼 보이지만, 실제 값 저장은 “스레드 내부”에서 분리되어 일어난다.
스레드 안전성(Thread-Safety)
스레드마다 독립 저장이므로 동기화(synchronized) 없이도 경쟁 조건을 피할 수 있다.
간편한 상태 유지
요청-응답 동안 필요한 상태(예: 인증된 사용자 정보)를 파라미터로 계속 전달하지 않고 유지할 수 있다.
코드 간결성
전역 변수/싱글톤에 상태를 저장하지 않고도 요청 스코프 데이터를 다룰 수 있어 구조가 깔끔해진다.
ThreadLocal 자체는 편하지만, 서버는 보통 스레드 풀(Thread Pool) 구조로 동작한다.
그래서 요청이 끝나는 시점에 반드시 정리해야 한다.
Spring Boot 내장 Tomcat은 기본적으로 스레드 풀을 사용한다.
application.yml에서 조절할 수 있다.
예시(형태만 참고):
server:
tomcat:
max-threads: 200 # 최대 워커 스레드 수(기본값)
min-spare-threads: 10 # 최소 여유 스레드 수(기본값)

1) 요청이 들어오면 Tomcat 스레드 풀에서 스레드 하나를 할당
2) 하나의 HTTP 요청은 시작~끝까지 보통 같은 스레드에서 처리
3) 인증 필터(예: Spring Security Filter)에서 인증 성공 시 ThreadLocal에 사용자 정보 저장
4) Controller/Service에서 ThreadLocal.get()으로 사용자 정보 사용
5) 응답 반환
6) finally 등에서 반드시 ThreadLocal.remove()로 정리
7) 스레드가 풀로 반납
따라서
remove()는 선택이 아니라 “필수”에 가깝다.
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class UserContext {
private static final ThreadLocal<User> userThreadLocal =
ThreadLocal.withInitial(() -> null);
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
public static void clear() {
userThreadLocal.remove();
}
}
setUser() : 현재 스레드에 사용자 저장getUser() : 현재 스레드의 사용자 조회clear() : 현재 스레드의 ThreadLocal 값 제거 (요청 끝나면 반드시)Spring Security Filter는 컨트롤러에 도달하기 전에 동작하는 보안 필터 체인(Filter Chain)의 일부다.
요청이 들어오면 “중간 관문”처럼 인증(Authentication)과 인가(Authorization)를 처리한다.

Client → Servlet Container
→ Filter 단계
→ (Spring Security Filter Chain: 인증/인가, 세션 관리, CSRF/CORS 등)
→ DispatcherServlet(Front Controller)
→ Interceptor 단계(컨트롤러 전/후 공통 로직)
→ Controller / Service / Repository
→ Response
Security를 본격적으로 들어가면 “요청 흐름(Filter → DispatcherServlet → Controller)”이 계속 핵심이 된다.
ThreadLocal은 인증된 사용자 상태를 “요청 단위로 유지”하는 데 유용하지만,
스레드 풀 재사용 때문에 remove() 정리 습관이 없으면 바로 사고 난다는 점이 가장 중요하다.