오봉이와 함께하는 개발 블로그

스프링 핵심 원리 - 고급편_쓰레드 로컬(동시성 문제 - 예제 코드) 본문

BE/Spring

스프링 핵심 원리 - 고급편_쓰레드 로컬(동시성 문제 - 예제 코드)

오봉봉이 2024. 3. 17. 23:36
728x90

동시성 문제 - 예제 코드

테스트에서도 lombok을 사용하기 위해 다음 코드를 추가하자.

build.gradle

 dependencies {
     ...
//테스트에서 lombok 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

이렇게 해야 테스트 코드에서 @Slfj4 같은 애노테이션이 작동한다.

FieldService(테스트 코드)

package com.example.advanced.trace.threadlocal.code;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public 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 (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.example.advanced.trace.threadlocal;

import com.example.advanced.trace.threadlocal.code.FieldService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@Slf4j
public class FieldServiceTest {
    private FieldService fieldService = new FieldService();

    @Test
    void field() {
        log.info("main start");
        Runnable userA = () -> {
            fieldService.logic("userA");
        };

        Runnable userB = () -> {
            fieldService.logic("userB");
        };

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A");
        Thread threadB = new Thread(userB);
        threadB.setName("thread-B");

        threadA.start(); //A실행
        sleep(2000); //동시성 문제 발생X
        // sleep(100); //동시성 문제 발생O
        threadB.start(); //B실행
        sleep(3000); //메인 쓰레드 종료 대기 >> 마지막에 하지 않으면 그냥 field() 메서드가 종료되어 threadB 실행 결과가 나오지 않음
        log.info("main exit");
    }

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

순서대로 실행한 결과

image

[main] INFO com.example.advanced.trace.threadlocal.FieldServiceTest -- main start
[thread-A] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 저장 name=userA -> nameStore=null
[thread-A] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 조회 nameStore=userA
[thread-B] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 저장 name=userB -> nameStore=userA
[thread-B] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 조회 nameStore=userB
[main] INFO com.example.advanced.trace.threadlocal.FieldServiceTest -- main exit

순서대로 실행하지 않은 결과

S[main] INFO com.example.advanced.trace.threadlocal.FieldServiceTest -- main start
[thread-A] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 저장 name=userA -> nameStore=null
[thread-B] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 저장 name=userB -> nameStore=userA
[thread-A] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 조회 nameStore=userB
[thread-B] INFO com.example.advanced.trace.threadlocal.code.FieldService -- 조회 nameStore=userB
[main] INFO com.example.advanced.trace.threadlocal.FieldServiceTest -- main exit

저장은 문제가 없지만 조회에서 문제가 발생한다.

imageimageimage

  1. thread-AuserA를 먼저 nameStore에보관
  2. 0.1초 후 thread-BuserBnameStore에 보관하여 기존 userA가 제거되고 userB가 저장
  3. thread-A 호출이 끝나며 nameStore 결과 반환 받는데 앞에서 userB의 값으로 대체되어 userA가 반환되지 않음
  4. thread-B가 끝나며 userB 반환

간단하게 정리하면 아래와 같다.

  1. Thread-AuserAnameStore 에 저장했다.
  2. Thread-BuserBnameStore 에 저장했다.
  3. Thread-AuserBnameStore 에서 조회했다.
  4. Thread-BuserBnameStore 에서 조회했다.

동시성 문제

동시성 문제는 여러 쓰레드에서 같은 인스턴스 필드 값에 접근(조회, 변경)하면서 발생한다.
트래픽이 적은 상황에서는 잘 발생하지 않을 수 있지만, 트래픽이 많이지는 상황에서는 자주 발생한다.
특히 스프링 빈 처럼 싱글톤 객체의 필드에 접근할 때는 동시성 문제를 조심해야 한다.

메서드 내부에 선언한 지역 변수에는 발생하지 않는다. 이는 쓰레드마다 각각 다른 메모리가 할당되어 독립적으로 실행되기 때문이다. 동시성 문제가 발생하는 곳은 인스턴스 필드나 static 같은 공용 필드에서 발생한다. 또한, 읽기만 할 때는 상관없지만 쓰기를 수행하며 발생한다.

지금처럼 싱글톤 객체의 필드를 사용하며 동시성 문제를 해결하려면 쓰레드 로컬을 사용하자.

 

출처: 김영한 지식공유자의 스프링 핵심 원리 고급편

728x90
Comments