김영한의 스프링 핵심 원리(고급편) - (Log Trace, ThreadLocal)

2025. 2. 18. 22:48·김영한의 스프링 핵심 원리 - 고급편

Question

  • ThreadLocal은 언제 사용하면 좋은가?
  • ThreadLocal 사용시 꼭 주의해야 하는 점은?

 

ThreadLocal

  • 해당 스레드만 접근할 수 있는 특별한 저장소
  • 다른 스레드와는 공유되지 않음

 

ThreadLocal을 사용하면 좋은 경우

  • 아래 예제에서 아래 코드가 쓰레드 로컬을 선언한 부분
    private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>();
  • 만약에 쓰레드 로컬을 사용하지 않았다면 아래와 같이 선언을 해서 사용을 했을 것임.
    private TraceId traceIdHolder;
  • 해당 클래스가 만약 Bean으로 사용된다면 Bean은 싱글톤 객체이므로 여러 스레드간 공유되는 자원이고, 싱글톤객체 사용시 가장 문제가 될 수 있는 Stateful 문제가 발생
  • 이는 Heap에 저장되는 공유 자원이 있는 경우 여러 스레드가 공유하게 되며, 이 값에 대한 조회가 아니라 쓰기 작업을 하게 될 경우 동시성 문제 때문에 예기치 못한 큰 문제가 발생할 수 있음
  • 따라서, 여러 쓰레드간에 공유되지 말아야할 자원에 대해서 ThreadLocal을 사용하면 좋음

 

 

ThreadLocal 사용시 주의 점

  • 스레드는 일반적으로 사용이 완료된 후 제거되는게 아니라 스레드풀로 돌아가 재사용 되므로, 그 경우 ThreadLocal은 그 스레드에 남아서 새로운 요청에 재사용될 수있음
  • 이는 내 계좌 정보가 다른 사람에게 보여지는것과 같은 치명적인 문제를 야기할 수 있으므로 ThreadLocal은 모든 작업이 끝나면 꼭 제거를 해줘야 함.

 

ThreadLocal을 적용한 Log Trace 예시

@Slf4j
public class ThreadLocalLogTrace implements LogTrace {
    private static final String START_PREFIX = "-->";
    private static final String COMPLETE_PREFIX = "<--";
    private static final String EX_PREFIX = "<X-";
    private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>();

    @Override
    public TraceStatus begin(String message) {
        syncTraceId();
        TraceId traceId = traceIdHolder.get();
        Long startTimeMs = System.currentTimeMillis();
        log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
        return new TraceStatus(traceId, startTimeMs, message);
    }

    @Override
    public void end(TraceStatus status) {
        complete(status, null);
    }

    @Override
    public void exception(TraceStatus status, Exception e) {
        complete(status, e);
    }

    private void complete(TraceStatus status, Exception e) {
        Long stopTimeMs = System.currentTimeMillis();
        long resultTimeMs = stopTimeMs - status.getStartTimeMs();
        TraceId traceId = status.getTraceId();
        if (e == null) {
            log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), 
                     status.getMessage(), resultTimeMs);
        } else {
            log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), 
                     status.getMessage(), resultTimeMs, e.toString());
        }
        releaseTraceId();
    }

    private void syncTraceId() {
        TraceId traceId = traceIdHolder.get();
        if (traceId == null) {
            traceIdHolder.set(new TraceId());
        } else {
            traceIdHolder.set(traceId.createNextId());
        }
    }

    private void releaseTraceId() {
        TraceId traceId = traceIdHolder.get();
        if (traceId.isFirstLevel()) {
            traceIdHolder.remove(); // destroy
        } else {
            traceIdHolder.set(traceId.createPreviousId());
        }
    }

    private static String addSpace(String prefix, int level) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < level; i++) {
            sb.append( (i == level - 1) ? "|" + prefix : "| ");
        }
        return sb.toString();
    }
}



@Configuration
public class LogTraceConfig {

    @Bean
    public LogTrace logTrace() {
        // return new FieldLogTrace();
        return new ThreadLocalLogTrace();
    }
}




@RestController
@RequiredArgsConstructor
public class OrderControllerV3 {

    private final OrderServiceV3 orderService;
    private final LogTrace trace;

    @GetMapping("/v3/request")
    public String request(String itemId) {
        TraceStatus status = null;
        try {
            status = trace.begin("OrderController.request()");
            orderService.orderItem(itemId);
            trace.end(status);
            return "ok";
        } catch (Exception e) {
            trace.exception(status, e);
            throw e; // 예외를 꼭 다시 던져주어야 한다.
        }
    }
}



@Service
@RequiredArgsConstructor
public class OrderServiceV3 {

    private final OrderRepositoryV3 orderRepository;
    private final LogTrace trace;

    public void orderItem(String itemId) {
        TraceStatus status = null;
        try {
            status = trace.begin("OrderService.orderItem()");
            orderRepository.save(itemId);
            trace.end(status);
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }
}




@Repository
@RequiredArgsConstructor
public class OrderRepositoryV3 {

    private final LogTrace trace;

    public void save(String itemId) {
        TraceStatus status = null;
        try {
            status = trace.begin("OrderRepository.save()");
            // 저장 로직
            if (itemId.equals("ex")) {
                throw new IllegalStateException("예외 발생!");
            }
            sleep(1000);
            trace.end(status);
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

 

 

'김영한의 스프링 핵심 원리 - 고급편' 카테고리의 다른 글

김영한의 스프링 핵심 원리(고급편) - 빈 후처리기  (0) 2025.02.22
김영한의 스프링 핵심 원리(고급편) - 프록시 팩토리  (0) 2025.02.22
김영한의 스프링 핵심 원리(고급편) - 동적 프록시 기술  (0) 2025.02.21
김영한의 스프링 핵심 원리(고급편) - 프록시 패턴, 데코레이터 패턴  (0) 2025.02.21
김영한의 스프링 핵심 원리(고급편) - 템플릿 메서드 패턴, 전략 패턴, 템플릿 콜백 패턴  (0) 2025.02.20
'김영한의 스프링 핵심 원리 - 고급편' 카테고리의 다른 글
  • 김영한의 스프링 핵심 원리(고급편) - 프록시 팩토리
  • 김영한의 스프링 핵심 원리(고급편) - 동적 프록시 기술
  • 김영한의 스프링 핵심 원리(고급편) - 프록시 패턴, 데코레이터 패턴
  • 김영한의 스프링 핵심 원리(고급편) - 템플릿 메서드 패턴, 전략 패턴, 템플릿 콜백 패턴
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (242)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (118)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      gesingleresult
      cglib
      requset scope
      자바
      조회 성능 최적화
      빈 후처리기
      @args
      김영한
      JPQL
      프록시 팩토리
      @discriminatorvalue
      Target
      hibernate5module
      jdk 동적 프록시
      프록시
      단방향 맵핑
      reentarantlock
      WAS
      typequery
      페치 조인
      Thread
      @discriminatorcolumn
      고급
      jpq
      버퍼
      @within
      양방향 맵핑
      log trace
      락
      스레드
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    김영한의 스프링 핵심 원리(고급편) - (Log Trace, ThreadLocal)
    상단으로

    티스토리툴바