Category: ThreadLocal

0

Spring 핵심원리 고급편 - ThreadLocal

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 ThreadLocal@Configurationpublic class LogTraceConfig {// @Bean// public LogTrace logTrace(){// return new FieldLogTrace();// } @Bean public LogTrace logTrace(){ return new ThreadLocalLogTrace(); }} Thread 별로 로그가 정확하게 나눠진 것을 확인할 수 있다. 2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request()2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |-->OrderService.orderItem()2021-12-06 02:15:37.486 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |-->OrderRepository.save()2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |<--OrderRepository.save() time=1002ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |<--OrderService.orderItem() time=1003ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request() time=1003ms 2021-12-06 02:15:37.648 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] OrderController.request()2021-12-06 02:15:37.648 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] |-->OrderService.orderItem()2021-12-06 02:15:37.648 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] | |-->OrderRepository.save()2021-12-06 02:15:38.652 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] | |<--OrderRepository.save() time=1004ms2021-12-06 02:15:38.653 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] |<--OrderService.orderItem() time=1005ms2021-12-06 02:15:38.653 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] OrderController.request() time=1005ms

0

Spring 핵심원리 고급편 - ThreadLocal 적용

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 ThreadLocal 적용@Slf4jpublic 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; // traceId 동기화, 동시성 이슈 @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); } private void syncTraceId(){ TraceId traceId = traceIdHolder.get(); if(traceId == null){ traceIdHolder.set(new TraceId()); }else{ traceIdHolder.set(traceId.createNextId()); } } @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 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(); }} class ThreadLocalLogTraceTest { ThreadLocalLogTrace trace = new ThreadLocalLogTrace(); @Test void begin_end_level2(){ TraceStatus status1 = trace.begin("hello1"); TraceStatus status2 = trace.begin("hello2"); trace.end(status2); trace.end(status1); } @Test void begin_end_level2_exception(){ TraceStatus status1 = trace.begin("hello1"); TraceStatus status2 = trace.begin("hello2"); trace.exception(status2, new IllegalStateException()); trace.exception(status1, new IllegalStateException()); }} 15:20:49.175 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] hello115:20:49.179 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] |-->hello215:20:49.180 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] |<--hello2 time=3ms15:20:49.180 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] hello1 time=6ms @Configurationpublic class LogTraceConfig {// @Bean// public LogTrace logTrace(){// return new FieldLogTrace();// } @Bean public LogTrace logTrace(){ return new ThreadLocalLogTrace(); }} Thread 별로 로그가 정확하게 나눠진 것을 확인할 수 있다. 2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request()2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |-->OrderService.orderItem()2021-12-06 02:15:37.486 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |-->OrderRepository.save()2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |<--OrderRepository.save() time=1002ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |<--OrderService.orderItem() time=1003ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request() time=1003ms

0

Spring 핵심원리 고급편 - ThreadLocal

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 ThreadLocal ThreadLocal은 해당 Thread 만 접근할 수 있는 저장소다ThreadLocal을 사용하게 되면 각 Thread마다 별도의 저장소를 제공한다. 사용법 값 저장 : ThreadLocal.set(value) 값 조회 : ThreadLocal.get() 값 삭제 : ThreadLocal.remove() 해당 Thread 가 ThreadLocal 을 모두 사용하고 나면 ThreadLocal.remove() 를 호출해 ThreadLocal 에 저장된 값을 제거해 줘야 한다. @Slf4jpublic class ThreadLocalService { private ThreadLocal<String> nameStore = new ThreadLocal<>(); public String logic(String name) { log.info("저장 name={} -> nameStore = {}", name, nameStore.get()); nameStore.set(name); sleep(1000); log.info("조회 nameStore = {}", nameStore.get()); return nameStore.get(); } private void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } }} @Slf4jpublic class ThreadLocalServiceTest { private ThreadLocalService service = new ThreadLocalService(); @Test public void field(){ log.info("main start"); Runnable userA = () -> { service.logic("userA"); }; Runnable userB = () -> { service.logic("userB"); }; Thread threadA = new Thread(userA); threadA.setName("thread - A"); Thread threadB = new Thread(userB); threadB.setName("thread - B"); threadA.start(); sleep(100); threadB.start(); sleep(2000); // 메인 Thread 종료 대기 } private void sleep(int millis){ try { Thread.sleep(millis); }catch (Exception e){ e.printStackTrace(); } }} 15:09:42.290 [Test worker] INFO com.example.advancedspring.trace.threadlocal.ThreadLocalServiceTest - main start15:09:42.293 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 저장 name=userA -> nameStore = null15:09:42.397 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 저장 name=userB -> nameStore = null15:09:43.301 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 조회 nameStore = userA15:09:43.400 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 조회 nameStore = userB15:09:44.402 [Test worker] INFO com.example.advancedspring.trace.threadlocal.ThreadLocalServiceTest - main exit

0

Spring 핵심원리 고급편 - 동시성 이슈

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 동시성 이슈 발생 여러 Thread가 동시에 Application 로직을 호출하게 되면서 Level 상태값이 꼬여서 보이게 된다. Thread 로 구분해서 로그 확인2021-12-06 02:18:26.028 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] OrderController.request()2021-12-06 02:18:26.029 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |-->OrderService.orderItem()2021-12-06 02:18:26.029 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | |-->OrderRepository.save()2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | |<--OrderRepository.save() time=1004ms2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |<--OrderService.orderItem() time=1004ms2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] OrderController.request() time=1005ms 2021-12-06 02:18:26.114 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |-->OrderController.request()2021-12-06 02:18:26.115 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |-->OrderService.orderItem()2021-12-06 02:18:26.115 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | | |-->OrderRepository.save()2021-12-06 02:18:27.119 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | | |<--OrderRepository.save() time=1004ms2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |<--OrderService.orderItem() time=1005ms2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |<--OrderController.request() time=1006ms Thread 를 이용한 테스트 코드 작성@Slf4jpublic class FieldService { private String nameStore; public String logic(String name){ log.info("저장 name={} -> nameStore = {}", name, nameStore); nameStore = name; sleep(1000); log.info("조회 nameStore = {}", nameStore); return nameStore; } private void sleep(int millis) { try { Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - 필드 동기화 적용

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 필드 동기화 적용Bean 으로 등록@Configuration public class LogTraceConfig { @Bean public LogTrace logTrace() { return new FieldLogTrace(); } } @RestController@RequiredArgsConstructorpublic class OrderControllerV3 { private final OrderServiceV3 orderService; private final LogTrace trace; @GetMapping("/v3/request") public String request(String itemId){ System.out.println(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@RequiredArgsConstructorpublic 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@RequiredArgsConstructorpublic 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

Spring 핵심원리 고급편 - 필드 동기화

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 필드 동기화public interface LogTrace { TraceStatus begin(String message); void end(TraceStatus status); void exception(TraceStatus status, Exception e);} 필드 동기화를 위한 로그 분석기 만들기 TraceId 객체가 없을 경우 새로 생성한다. TraceId 객체가 있을 경우 Level 을 증가 시킨다. private void syncTraceId(){ if(traceIdHolder == null){ traceIdHolder = new TraceId(); }else{ traceIdHolder = traceIdHolder.createNextId(); }} TraceId 내 Level 이 0일 경우 해당 객체를 null 로 초기화 한다. TraceId 내 Level 이 0이 아닐 경우 Level을 1 감소시킨다. private void releaseTraceId() { if(traceIdHolder.isFirstLevel()){ traceIdHolder = null; // destroy }else{ traceIdHolder = traceIdHolder.createPreviousId(); }} TraceId 객체는 파라미터로 전달되는 것이 아니라 traceIdHolder 필드에 저장된다.