Field
@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); //동시성 문제 발생X
sleep(100); //동시성 문제 발생O
threadB.start();
sleep(2000); //메인 쓰레드 종료 대기
log.info("main exit");
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 필드로 하면 동시성 문제가 생긴다. 값을 변경하는 부분에서 동시성 이슈로 꼬이게 된다.
ThreadLocal
@Slf4j
public class ThreadLocalService {
private ThreadLocal<String> nameStore = new ThreadLocal<>();
public String logic(String name) {
log.info("저장 name={} -> nameStore={}", name, nameStore.get());
nameStore.set(name);
sleep(1000);
log.info("조회 nameStore={}", nameStore.get());
return nameStore.get();
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Slf4j
public class ThreadLocalServiceTest {
private ThreadLocalService service = new ThreadLocalService();
@Test
void field() {
log.info("main start");
Runnable userA = () -> {
service.logic("userA");
};
Runnable userB = () -> {
service.logic("userB");
};
Thread threadA = new Thread(userA);
threadA.setName("thread-A");
Thread threadB = new Thread(userB);
threadB.setName("thread-B");
threadA.start();
// sleep(2000); //동시성 문제 발생X
sleep(100); //동시성 문제 발생O
threadB.start();
sleep(2000); //메인 쓰레드 종료 대기
log.info("main exit");
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 쓰레드 로컬은 해당 쓰레드만 접근할 수 있는 특별한 저장소이다.
ThreadLocal 사용법
- 값 저장: ThreadLocal.set(xxx)
- 값 조회: ThreadLocal.get()
- 값 제거: ThreadLocal.remove()
※ 해당 쓰레드가 쓰레드 로컬을 모두 사용하고 나면 ThreadLocal.remove() 를 호출해서 쓰레드 로컬에 저장된 값을 제거해주어야 한다.
※ 이런 동시성 문제는 지역 변수에서는 발생하지 않는다. 동시성 문제가 발생하는 곳은 같은 인스턴스의 필드(주로 싱글톤에서 자주 발생), 또는 static 같은 공용 필드에 접근할 때 발생한다. 동시성 문제는 값을 읽기만 하면 발생하지 않는다. 어디선가 값을 변경하기 때문에 발생한다.
'개발이 좋아서 > Spring이 좋아서' 카테고리의 다른 글
디자인 패턴 - 전략 패턴(템플릿 콜백 패턴) (0) | 2024.12.29 |
---|---|
디자인 패턴 - 템플릿 메서드 패턴 (0) | 2024.12.29 |
빈 스코프 - 웹 스코프(Provider/프록시) (0) | 2024.12.28 |
빈 스코프 - 프로토타입 스코프 (0) | 2024.12.28 |
빈 생명주기 콜백 (1) | 2024.12.28 |