오봉이와 함께하는 개발 블로그
스프링 핵심 원리 - 고급편_쓰레드 로컬(동시성 문제 - 예제 코드) 본문
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();
}
}
}
순서대로 실행한 결과
[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
저장은 문제가 없지만 조회에서 문제가 발생한다.
thread-A
가userA
를 먼저nameStore
에보관- 0.1초 후
thread-B
가userB
를nameStore
에 보관하여 기존userA
가 제거되고userB
가 저장 thread-A
호출이 끝나며nameStore
결과 반환 받는데 앞에서userB
의 값으로 대체되어userA
가 반환되지 않음thread-B
가 끝나며userB
반환
간단하게 정리하면 아래와 같다.
Thread-A
는userA
를nameStore
에 저장했다.Thread-B
는userB
를nameStore
에 저장했다.Thread-A
는userB
를nameStore
에서 조회했다.Thread-B
는userB
를nameStore
에서 조회했다.
동시성 문제
동시성 문제는 여러 쓰레드에서 같은 인스턴스 필드 값에 접근(조회, 변경)하면서 발생한다.
트래픽이 적은 상황에서는 잘 발생하지 않을 수 있지만, 트래픽이 많이지는 상황에서는 자주 발생한다.
특히 스프링 빈 처럼 싱글톤 객체의 필드에 접근할 때는 동시성 문제를 조심해야 한다.
메서드 내부에 선언한 지역 변수에는 발생하지 않는다. 이는 쓰레드마다 각각 다른 메모리가 할당되어 독립적으로 실행되기 때문이다. 동시성 문제가 발생하는 곳은 인스턴스 필드나 static 같은 공용 필드에서 발생한다. 또한, 읽기만 할 때는 상관없지만 쓰기를 수행하며 발생한다.
지금처럼 싱글톤 객체의 필드를 사용하며 동시성 문제를 해결하려면 쓰레드 로컬
을 사용하자.
출처: 김영한 지식공유자의 스프링 핵심 원리 고급편
728x90
'BE > Spring' 카테고리의 다른 글
스프링 핵심 원리 - 고급편_쓰레드 로컬(쓰레드 로컬 동기화 개발, 적용) (1) | 2024.03.17 |
---|---|
스프링 핵심 원리 - 고급편_쓰레드 로컬(쓰레드 로컬 소개, 예제 코드) (0) | 2024.03.17 |
스프링 핵심 원리 - 고급편_쓰레드 로컬(필드 동기화 적용, 동시성 문제) (0) | 2024.03.17 |
스프링 핵심 원리 - 고급편_쓰레드 로컬(필드 동기화 - 개발) (0) | 2024.03.17 |
스프링 핵심 원리 - 고급편_로그 추적기(V2 적용) (1) | 2024.03.17 |
Comments