Java에서 멀티스레드 환경을 개발하다 보면, 스레드마다 독립적인 데이터를 저장하고 관리해야 할 때가 있습니다.
이럴 때 유용하게 활용할 수 있는 클래스가 바로 ThreadLocal입니다.
이번 글에서는 ThreadLocal의 개념, 사용 방법, 그리고 실제 사례를 중심으로 살펴보겠습니다.
ThreadLocal이란?
ThreadLocal은 각 스레드마다 별도의 변수를 저장할 수 있도록 지원하는 Java 클래스입니다.
동일한 ThreadLocal 인스턴스를 사용하더라도, 각 스레드가 자신만의 독립적인 값을 가지며, 다른 스레드와 공유되지 않습니다.
즉, 하나의 변수를 스레드 간에 안전하게 관리할 수 있는 메커니즘을 제공합니다.
ThreadLocal의 특징
- 각 스레드마다 독립적인 데이터 저장 공간을 제공합니다.
- 스레드 간 데이터 공유를 방지하며, 멀티스레드 환경에서 데이터 충돌을 줄입니다.
- 특정 스레드에 한정된 데이터를 저장할 때 유용합니다.
ThreadLocal의 사용 방법
ThreadLocal은 주로 다음과 같은 메소드를 사용하여 데이터를 저장하고 접근합니다.
set()
현재 스레드의 ThreadLocal 변수에 값을 저장합니다.
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Thread A's Data");
get()
현재 스레드의 ThreadLocal 변수에 저장된 값을 가져옵니다.
String data = threadLocal.get();
System.out.println(data);
remove()
현재 스레드의 ThreadLocal 변수에 저장된 값을 삭제합니다. 이는 메모리 누수를 방지하기 위해 권장되는 작업입니다.
threadLocal.remove();
initialValue()
ThreadLocal 변수를 초기화할 때 사용됩니다. 기본적으로 null을 반환하지만, 필요하면 오버라이딩하여 초기값을 설정할 수 있습니다.
ThreadLocal<Integer> threadLocal = new ThreadLocal<>() {
@Override
protected Integer initialValue() {
return 0;
}
};
System.out.println(threadLocal.get());
사용 시 주의사항
ThreadLocal은 사용 후 반드시 remove() 메소드를 호출하여 데이터를 정리해야 합니다.
이를 소홀히 하면, 스레드가 종료된 후에도 ThreadLocal 객체에 데이터가 남아 메모리 누수가 발생할 수 있습니다.
또한, 너무 많은 데이터를 ThreadLocal에 저장할 경우 OOM을 야기할 수 있으니 적절하게 사용하는 것을 권장합니다.
ThreadLocal은 부모 Thread가 자식 Thread로 데이터를 전달하지 않습니다.
따라서 데이터를 상속하기 위해서는 ThreadLocal를 상속한 InheritableThreadLocal를 사용하시면 됩니다.
사용해 보기
ThreadLocal 정의
@Getter
@Setter
public class CustomContext {
private static final ThreadLocal<CustomContext> CONTEXT = ThreadLocal.withInitial(CustomContext::new);
private Object data;
public static CustomContext getContext() {
return CONTEXT.get();
}
public static void clearContext() {
CONTEXT.remove();
}
}
JWT Token 유저 ID ThreadLocal에 저장 및 사용 후 정리
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthenticationInterceptor implements HandlerInterceptor {
private final JwtTokenProvider jwtTokenProvider;
private final CustomContext customContext = CustomContext.getContext();;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
if(!(handler instanceof HandlerMethod))
return true;
String token = request.getHeader("authorization");
if(Strings.isEmpty(token)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT token is missing");
return false;
}
token = token.substring(7);
if(!jwtTokenProvider.validateToken(token)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT Token is invalid");
return false;
}
String userId = jwtTokenProvider.getUserId(token);
customContext.setData(userId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
CustomContext.clearContext();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
ThreadLocal 데이터 사용
@Getter
@Setter
public class CreateQuoteRequest {
transient private String userId;
private long amount;
private CurrencyCode targetCurrency;
public CreateQuoteRequest() {
CustomContext context = CustomContext.getContext();
Assert.notNull(context);
this.userId = (String) context.getData();
}
}
후기
기존 ID를 넘겨줄 때 Token에서 뽑아 HttpServletRequest에 담거나 하여 사용을 하였지만,
이와 같이 ThreadLocal을 사용하게 된다면 본인 Thread 내에서 데이터를 쉽게 뽑아 사용할 수 있다는 점이 매력적으로 다가온 기술입니다.
물론 메모리에 올려두고 사용한다는 점에서 비효율적일 수 있지만, 코드를 생각했을 때 좀 더 보기 편하고 관리하기 편하다는 점이 부정할 수 없다 생각하는데요.
추후 좀 더 관리하기 좋은 방법을 찾게 된다면 포스팅하도록 하겠습니다.
'Java' 카테고리의 다른 글
오차 없는 실수 연산 어떻게 해야 할까? (0) | 2025.01.10 |
---|---|
[Java] Static (3) | 2024.12.26 |
[ Basic ] I/O Stream이란? (0) | 2024.12.23 |
[JPA] 비관적 락 낙관적 락 (0) | 2024.06.29 |
JPA는 어떤 기술일까? (0) | 2024.04.12 |