동시성 문제
* 스프링빈으로 등록된 클래스가 싱글톤이라면, 이 객체는 애플리케이션에 딱 하나만 존재한다는 뜻이다.
이렇게 하나만 있는 인스턴스의 필드를 여러 쓰레드가 동시에 접근하면서 발생하는 문제
환경 구축
- test환경 lombok 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//테스트에서 롬복사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
- 동시성 문제가 발생하지 않는 경우
@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
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(2000); // 동시성 문제가 발생안하는 코드
threadB.start();
sleep(3000);
log.info("main exit");
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//00:28:31.987 [Test worker] INFO hello.advanced.threadlocal.FieldServiceTest - main start
//00:28:31.995 [thread-A] INFO hello.advanced.threadlocal.code.FieldService - 저장 name=userA -> nameStore=null
//00:28:33.012 [thread-A] INFO hello.advanced.threadlocal.code.FieldService - 조회 nameStore=userA
//00:28:34.010 [thread-B] INFO hello.advanced.threadlocal.code.FieldService - 저장 name=userB -> nameStore=userA
//00:28:35.021 [thread-B] INFO hello.advanced.threadlocal.code.FieldService - 조회 nameStore=userB
//00:28:37.015 [Test worker] INFO hello.advanced.threadlocal.FieldServiceTest - main exit
}
- A요청이 들어오고, 처음 nameStore는 처음에 값이 없으므로 null이 찍힘
- A요청이 완전히 끝나고 B요청이 들어오면 nameStore에는 A요청의 저장값인 userA가 남아있음
- 그리고 userB의 요청이 처리되고 종료됨(A유저의 값이 남아있는게 찝찝하지만 로직상에는 문제가 없음)
- 동시성 문제가 있는 코드
@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();
sleep(100);
threadB.start();
sleep(3000);
log.info("main exit");
//00:40:19.101 [thread-A] INFO hello.advanced.threadlocal.code.FieldService - 저장 name=userA -> nameStore=null
//00:40:19.203 [thread-B] INFO hello.advanced.threadlocal.code.FieldService - 저장 name=userB -> nameStore=userA
//00:40:20.121 [thread-A] INFO hello.advanced.threadlocal.code.FieldService - 조회 nameStore=userB
//00:40:20.215 [thread-B] INFO hello.advanced.threadlocal.code.FieldService - 조회 nameStore=userB
//00:40:22.206 [Test worker] INFO hello.advanced.threadlocal.FieldServiceTest - main exit
}
- userA의 요청이 들어오고 nameStore에 userA를 저장함
- userB가 userA의 요청이 끝나기 전에 들어와서 nameStore의값을 userB로 저장함
- userA의 요청과 userB의 요청이 nameStore가 userB로 나오는 문제가 발생
동시성 문제는 지역변수에서는 발생하지 않는다. 지역 변수는 쓰레드마다 각각 다른 메모리 영역이 할당되기 때문..
동시성 문제가 발생하는 곳은 같은 인스턴스의 필드, 또는 static 같은 공용 필드에 접근할 때 발생한다.
동시성 문제는 값을 읽기만 하면 발생하지 않는다.
'SPRING > 스프링' 카테고리의 다른 글
[스프링 핵심 원리-고급] 템플릿 콜백 패턴 (0) | 2022.01.27 |
---|---|
[스프링 핵심 원리 -고급] 전략패턴 (0) | 2022.01.27 |
[스프링 핵심 원리-고급] 템플릿 메서드 패턴 (0) | 2022.01.25 |
[스프링 핵심 원리-고급] 쓰레드 로컬 주의사항 (0) | 2022.01.22 |
[스프링 핵심 원리-고급] ThreadLocal (0) | 2022.01.21 |