스프링/스프링 핵심 원리 - 고급편

9. 필드 동기화

sdafdq 2024. 1. 14. 19:02

그냥 빈으로 (즉 디폴트는 싱글톤으로) 등록하고, 필드에 가지고 있게끔 해서 동기화 시키는 거다.

 

근데 이건 동시성 이슈가 있다.

 

싱글톤이기에 공유가 된다.

 

public interface LogTrace {
    TraceStatus begin(String message);
    void end(TraceStatus status);
    void exception(TraceStatus status, Exception e);
}

일단 인터페이스로 만든다. 갈아끼울 것이다.

 

@Slf4j
public class FieldLogTrace implements LogTrace{
    private static final String START_PREFIX = "-->";
    private static final String COMPLETE_PREFIX = "<--";
    private static final String EX_PREFIX = "<X-";

    private TraceId traceIdHolder;

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

        return new TraceStatus(traceId, startTimeMs, message);
    }

    private void syncTraceId(){
        if(traceIdHolder == null){
            traceIdHolder = new TraceId();
        }else{
            traceIdHolder = traceIdHolder.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() {
        if(traceIdHolder.isFirstLevel()){
            traceIdHolder = null;
        }else{
            traceIdHolder = traceIdHolder.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();
    }
}

기존이랑 거의 똑같다. 달라진 점은,

 

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

    return new TraceStatus(traceId, startTimeMs, message);
}

여기서 syncTraceId()를 쓴다는 점.

 

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

필드에 traceIdHolder라는 곳에 기억해 놓는 것이다.

 

만약 필드가 null이면 새로 만들고, 만약 있는데 syncTraceId를 다시 호출했다?

그러면 레벨을 하나 올리는 것 이다.

 

 

또 complete 부분도 바뀌었다.

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();
}

releaseTraceId() 하면

 

private void releaseTraceId() {
    if(traceIdHolder.isFirstLevel()){
        traceIdHolder = null;
    }else{
        traceIdHolder = traceIdHolder.createPreviousId();
    }
}

레벨이 first레벨인데 release 시키는 거면 끝이니 그냥 null로, 즉 없에주는 거고,

first가 아니면 이전 레벨로 변화 시킨다.

 

 

@Test
void begin_end_level2(){
    TraceStatus status1 = trace.begin("hello1");
    TraceStatus status2 = trace.begin("hello2");

    trace.end(status2);
    trace.end(status1);
}

테스트 잘 된다.

begin일 때 syncTraceId라서 필드 보고 판단하는 거라서 알아서 잘 된다.

 

 

하지만, 동시성 문제가 있다.

 

 

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

11. 동시성 문제  (0) 2024.01.14
10. 필드 동기화 적용  (0) 2024.01.14
8. V2 정리  (0) 2024.01.14
7. V2 적용  (0) 2024.01.14
6. V2 파라미터로 동기화  (0) 2024.01.14