
Spring에서는 현재 요청, 인증 정보, 트랜잭션 상태에 전역적으로 접근할 수 있습니다.
다음은 접근하는 예입니다.
// SecurityContextHolder 접근
SecurityContextHolder.getContext().getAuthentication()
// Transaction 활성 여부 체크
TransactionSynchronizationManager.isActualTransactionActive()
Spring은 요청 처리 중 필요한 상태를 ThreadLocal에 저장하여 현재 스레드에서 공유합니다. 그렇기 때문에 위와 같은 코드를 사용할 수 있는 것입니다.
오늘은 Spring 내부에서 ThreadLocal이 어떻게 사용되는지 살펴보겠습니다.
ThreadLocal은 Thread마다 독립적인 데이터를 저장하는 메커니즘입니다.
일반 변수는 모든 Thread가 공유하지만, ThreadLocal은 다릅니다. 이는 다시 말해, Thread마다 별도의 저장 공간을 갖는다는 뜻입니다.
다음은 예시 코드입니다.
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("user1");
String value = threadLocal.get();
// 위 값은 현재 Thread에서만 접근 가능.
ThreadLocal은 내부적으로 Thread -> ThreadLocalMap 구조를 사용합니다.
구조는 다음과 같습니다.
Thread
└── ThreadLocalMap
└── Entry(ThreadLocal, value)
실제 데이터는 ThreadLocal이 아니라, Thread 내부에 저장됩니다.
Spring에서는 ThreadLocal이 매우 많이 사용됩니다. 대표적으로 Transaction 관리, SecurityContextHolder, RequestContextHolder가 있습니다. 차례대로 살펴보겠습니다.
트랜잭션 상태는 ThreadLocal로 관리됩니다.
핵심 클래스는 다음과 같습니다.
TransactionSynchronizationManager
동작 흐름은 다음과 같습니다.
@Transactional
↓
TransactionInterceptor
↓
TransactionManager.begin()
↓
TransactionSynchronizationManager
↓
ThreadLocal 저장
현재 Thread에는 Connection, TransactionStatus, Synchronization 정보가 저장됩니다. 그래서 어디서든 TransactionSynchronizationManager.isActualTransactionActive()를 통해 현재 트랜잭션 상태를 확인할 수 있습니다.
SecurityContextHolder도 ThreadLocal을 사용합니다.
로그인 이후 구조는 다음과 같습니다.
Thread
└── SecurityContext
└── Authentication
└── Principal
그렇기에, 다음과 같은 코드가 어디서든 동작합니다.
Authentication authentication = SecurityContextHolder
.getContext()
.getAuthentication();
현재 로그인 유저 정보가 ThreadLocal에 저장되는 겁니다.
Spring MVC에서는 현재 HTTP 요청도 ThreadLocal로 관리합니다.
그래서 Controller가 아닌 곳에서도 요청에 접근할 수 있습니다.
HttpServletRequest request =
((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes())
.getRequest();
ThreadLocal의 가장 큰 문제는 메모리 누수입니다. 그 이유는 ThreadLocalMap 구조 때문입니다.
다음과 같은 구조때문에, ThreadLocal 객체가 GC되더라도 value는 남아 있을 수 있습니다.
Entry
key = WeakReference(ThreadLocal)
value = Strong Reference
그래서 다음과 같이 remove() 메서드가 반드시 필요합니다.
threadLocal.remove();
Spring도 요청이 끝나면 다음과 같은 메서드를 호출합니다.
TransactionSynchronizationManager.clear()
SecurityContextHolder.clearContext()
RequestContextHolder.resetRequestAttributes()
Spring에서의 핵심 기능인 Transaction 관리, SecurityContext, HTTP Request Context, Logging MDC는 전부 ThreadLocal 기반입니다. 즉, Spring은 요청을 처리하는 동안 필요한 상태를 ThreadLocal에 저장하여, 현재 Thread에 공유합니다. 하지만 ThreadLocal은 메모리 누수, Thread Pool 환경 문제, Virtual Thread 호환성 문제를 가질 수 있기에 조심히 사용해야 합니다. 그래서 Spring 내부에서도 요청이 끝나면 ThreadLocal을 반드시 정리합니다.
ThreadLocal은 Spring 내부 구조를 이해하기 위해 반드시 알아야 하는 개념입니다.