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

목차

동시성 이슈 발생

여러 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=1004ms
2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |<--OrderService.orderItem() time=1004ms
2021-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=1004ms
2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |<--OrderService.orderItem() time=1005ms
2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |<--OrderController.request() time=1006ms

Thread 를 이용한 테스트 코드 작성

@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 (InterruptedException e){
e.printStackTrace();
}
}
}
@Slf4j
public class FieldServiceTest {

private FieldService fieldService = new FieldService();


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

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

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

threadA.start();
sleep(2000);
threadB.start();
}

private void sleep(int millis){
try {
Thread.sleep(millis);
}catch (Exception e){
e.printStackTrace();
}
}
}
14:10:58.072 [Test worker] INFO com.example.advancedspring.trace.threadlocal.FieldServiceTest - main start
14:10:58.076 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 저장 name=userA -> nameStore = null
14:10:59.081 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 조회 nameStore = userA
14:11:00.078 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 저장 name=userB -> nameStore = userA
14:11:01.080 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 조회 nameStore = userB

동시성 이슈가 발생하는 코드

@Test
public 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();
sleep(100);
threadB.start();

sleep(2000); // 메인 Thread 종료 대기
}
14:32:12.377 [Test worker] INFO com.example.advancedspring.trace.threadlocal.FieldServiceTest - main start
14:32:12.381 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 저장 name=userA -> nameStore = null
14:32:12.483 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 저장 name=userB -> nameStore = userA
14:32:13.387 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 조회 nameStore = userB
14:32:13.489 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.FieldService - 조회 nameStore = userB

동시성 문제는 지역 변수에서는 발생하지 않는다. 지역 변수는 쓰레드마다 각각 다른 메모리 영역에 할당된다.
동시성 문제가 발생하는 곳은 같은 인스턴스의 필드 (주로 싱글톤에서 발생) 또는 static 같은 공용 필드에서 발생한다.

Share