카테고리 없음

동시성문제란? 쓰레드로컬이란?

이운형 2022. 3. 16. 23:15
반응형

# 인프런 김영한의 스프링 핵심 원리 - 고급편을 개인적으로 정리한 글입니다.

 

 

Q. 동시성 문제란?

=> 여러 쓰레드가 동시에 같은 인스턴스 필드값을 변경 하면서 발생하는 문제이다.

 

Q. 동시성 문제의 특징

1. 동시성 문제는 지역변수에서는 발생하지 않는다.

(지역변수는 쓰레드마다 각기 다른 메모리 영역에 위치)

 

2. 동시성 문제는 같은 인스턴스의 필드 혹은 static과 같은 공용 필드에 접근할때 발생한다,

3. 동시성 문제는 값을 읽을 때는 발생하지 않는다. 변경할 때  발생한다.

 

즉 싱글톤 객체의 필드에 값을 변경할때 동시성 문제가 발생한다.

 

Q. 동시성 문제의 해결 방법

 

해당 쓰레드만 접근할 수 있는 특별한 저장소 즉, 쓰레드 로컬을 만들어 사용한다.

 

// 고객 A의 물건 a를 보관해주는 쓰레드 로컬

// 고객 B의 물건 b를 보관해주는 쓰레드 로컬을 만들어서 보내준다.

// 물건 창구 같은 역할을 한다.

 

Q.  쓰레드 로컬 주의사항

쓰레드 로컬의 값을 get으로 사용한후 제거하지 않으면

WAS에 계속 값이 싸여 쓰레드풀이 죽게 되므로

꼭 remove해서 끝난 쓰레드는 지우도록 하자.

 

 

 

 

살펴보기


 

A와  B 가 순서대로 값을 입력했는데. 뒤에 입력된 B의 값이  A의값을 덮어쓰기 해버렸다? = 동시성문제

 

 

 

 

 

쓰레드 로컬로 인해 동시성 문제를 해결!

 

 

<동시성 문제 발생 코드>

 

TraceId를 직접 변수로 받아서 호출하게 되므로 // 패착의 원인

controller, repository , Service 코드를 거치면서

 

연속된 값이 들어온다면 꼬이게 되버린다.

@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 = this.traceIdHolder;
        long startTimeMs = System.currentTimeMillis();
        log.info("[{}] {}{}", traceId,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();
        }
    }

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

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

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

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

    }
}

 

<ThreadLocal 을 활용한 동시성 문제 해결>

 

ThreadLocal에 담아두고, get으로 조회해서 꺼내서 사용하는 방식이므로,

연속으로 와도 꼬일일이 없다.

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

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

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

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

    }
    private void complete(TraceStatus status, Exception e){
        long stopTimeMillis = System.currentTimeMillis();
        long resultTimeMs = stopTimeMillis - 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();
        }else{
            traceIdHolder.set(traceId.createPreviousId());
        }
    }

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

    }
}
반응형