728x90

Java에서 멀티스레드 환경을 개발하다 보면, 스레드마다 독립적인 데이터를 저장하고 관리해야 할 때가 있습니다.

이럴 때 유용하게 활용할 수 있는 클래스가 바로 ThreadLocal입니다.

 

이번 글에서는 ThreadLocal의 개념, 사용 방법, 그리고 실제 사례를 중심으로 살펴보겠습니다.

ThreadLocal이란?

ThreadLocal은 각 스레드마다 별도의 변수를 저장할 수 있도록 지원하는 Java 클래스입니다.

 

동일한 ThreadLocal 인스턴스를 사용하더라도, 각 스레드가 자신만의 독립적인 값을 가지며, 다른 스레드와 공유되지 않습니다.

즉, 하나의 변수를 스레드 간에 안전하게 관리할 수 있는 메커니즘을 제공합니다.

ThreadLocal의 특징

  1. 각 스레드마다 독립적인 데이터 저장 공간을 제공합니다.
  2. 스레드 간 데이터 공유를 방지하며, 멀티스레드 환경에서 데이터 충돌을 줄입니다.
  3. 특정 스레드에 한정된 데이터를 저장할 때 유용합니다.

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 내에서 데이터를 쉽게 뽑아 사용할 수 있다는 점이 매력적으로 다가온 기술입니다.

 

물론 메모리에 올려두고 사용한다는 점에서 비효율적일 수 있지만, 코드를 생각했을 때 좀 더 보기 편하고 관리하기 편하다는 점이 부정할 수 없다 생각하는데요.

 

추후 좀 더 관리하기 좋은 방법을 찾게 된다면 포스팅하도록 하겠습니다. 

728x90

'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

+ Recent posts