ThreadLocal

이창근·2023년 8월 8일
0

Spring공부

목록 보기
1/9

스프링을 사용하다보면 동시성 문제가 생길 수 있다. 두개 이상의 스레드에서 클래스의 인스턴스에 접근하는 상황을 예로 들 수 있다. 스프링에서는 스프링 빈 기능을 제공하고 자주 사용하기 때문에 위와 같은 상황이 많이 발생하는데, 이를 해결할 수 있는 ThreadLocal이라는 기능에 대해 학습하였다.

ThreadLocal은 각 쓰레드의 독립적인 저장소라고 생각할 수 있는데, 이를 통해 스프링 빈 클래스의 인스턴스를 각 쓰레드별로 다르게 유지할 수 있게 된다.

ThreadLocal을 사용하지 않았을 때 로그

[nio-8080-exec-3] [aaaaaaaa] OrderController.request()
[nio-8080-exec-3] [aaaaaaaa] |-->OrderService.orderItem()
[nio-8080-exec-3] [aaaaaaaa] | |-->OrderRepository.save()
[nio-8080-exec-4] [aaaaaaaa] | | |-->OrderController.request()
[nio-8080-exec-4] [aaaaaaaa] | | | |-->OrderService.orderItem()
[nio-8080-exec-4] [aaaaaaaa] | | | | |-->OrderRepository.save()
[nio-8080-exec-3] [aaaaaaaa] | |<--OrderRepository.save() time=1005ms
[nio-8080-exec-3] [aaaaaaaa] |<--OrderService.orderItem() time=1005ms
[nio-8080-exec-3] [aaaaaaaa] OrderController.request() time=1005ms
[nio-8080-exec-4] [aaaaaaaa] | | | | |<--OrderRepository.save() time=1005ms
[nio-8080-exec-4] [aaaaaaaa] | | | |<--OrderService.orderItem() time=1005ms
[nio-8080-exec-4] [aaaaaaaa] | | |<--OrderController.request() time=1005ms

위는 http요청이 오면 Controller, Service, Repository에서 로그를 출력하는 LogTrace클래스를 사용하여 만든 출력이다. http요청이 연속해서 오게 되니 동시성 문제가 발생하였다.

ThreadLocalLogTrace클래스 수정

public class ThreadLocalLogTrace {

    private final ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>();

    public TraceStatus begin(String message) {
        TraceId traceId = traceIdHolder.get();
        if (traceId == null) {
            traceIdHolder.set(new TraceId()); 
        } else {
        	traceIdHolder.set(traceId.createNextId());
        }
        TraceId traceId = traceIdHolder.get();
        return new TraceStatus(traceId, startTimeMs, message);
    }

동시성 문제를 ThreadLocal을 통해 해결한 클래스의 모습이다. 이렇게 인스턴스를 ThreadLocal로 바꾸는 것만으로 동시성 문제를 해결할 수 있었다.

ThreadLocal을 사용하였을 때 로그

[nio-8080-exec-3] [52808e46] OrderController.request()
[nio-8080-exec-3] [52808e46] |-->OrderService.orderItem()
[nio-8080-exec-3] [52808e46] | |-->OrderRepository.save()
[nio-8080-exec-4] [4568423c] OrderController.request()
[nio-8080-exec-4] [4568423c] |-->OrderService.orderItem()
[nio-8080-exec-4] [4568423c] | |-->OrderRepository.save()
[nio-8080-exec-3] [52808e46] | |<--OrderRepository.save() time=1001ms
[nio-8080-exec-3] [52808e46] |<--OrderService.orderItem() time=1001ms
[nio-8080-exec-3] [52808e46] OrderController.request() time=1003ms
[nio-8080-exec-4] [4568423c] | |<--OrderRepository.save() time=1000ms
[nio-8080-exec-4] [4568423c] |<--OrderService.orderItem() time=1001ms
[nio-8080-exec-4] [4568423c] OrderController.request() time=1001ms

이제는 동시성 문제가 해결되어 로그가 잘 출력되는 것을 볼 수 있었다. 앞으로는 여러 클래스를 설계할 때 동시성 문제를 적극적으로 고민할 수 있게 되었다.

주의점

쓰레드는 생성하는 비용이 아주 크기 때문에 보통 WAS는 쓰레드를 생성하는 것이 아니라 쓰레드풀에서 할당받아 사용하게 된다. 이때, ThreadLocal을 비우지 않고 쓰레드를 반환하게 되면 다음에 같은 쓰레드를 사용하였을 때 ThreadLocal에 값이 남아 있을 수도 있다. 따라서 ThreadLocal을 사용할 때에는 다 사용한 후에 ThreadLocal.remove() 함수를 호출하여 비워주도록 하자.

profile
나중에 또 모를 것들 모음

2개의 댓글

comment-user-thumbnail
2023년 8월 8일

좋은 글 감사합니다. 자주 방문할게요 :)

1개의 답글