일반적으로 두개 이상의 세션에서 공통된 데이터에 접근하여 읽거나 쓰는 작업이 이루어질 경우 발생 할 수 있는 문제
위와 같은 프로세스가 있다고 가정했을때 두개의 쓰레드가 같은 자원을 두고 읽고 쓰는 작업을 하고있다.
그러므로 더 늦게 시작한 Thread-B에 의해 Thread-A의 데이터가 무시되는 현상이 일어나게 된다. 이를 “손실되는 업데이트” 라고 한다.
Thread-A : "userA" 저장
Thread-B : "userB" 저장 -> "userA" 정보 사라짐
private String nameStore;
public String logic(String name){
log.info("저장 name={} --> nameStore={}", name, nameStore);
String localField = name;
nameStore = name;
sleep(1000);
log.info("조회 nameStore={}, localField={}", nameStore, localField);
return nameStore;
}
위와 같은 메소드가 있을때
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");
테스트 코드로 결과를 확인해 보면
nameStore 필드값이 나중에 셋팅된 Thread-B 값에 의해 꼬이게 된다.
해당 쓰레드만 접근할수있는 특별한 저장소이다.
물건 저장 창고를 떠올려보면, 창구직원이 사용자에 따라서 보관한 데이터를 구분해서 꺼내주기 때문에 제대로 된 데이터가 보장될 수 있다.
ThreadLocal은 각 쓰레드마다 별도의 내부 저장소를 제공한다.
데이터를 동시에 쓰게 되어도 ThreadLocal이 보관소를 구분해서 저장해줌
Thread-A : "userA" 저장 -> ThreadLocal Thread-A 전용 보관소에 저장해줌
Thread-B : "userB" 저장 -> ThreadLocal Thread-B 전용 보관소에 저장해줌
private ThreadLocal<String > nameStore = new ThreadLocal<>();
public String logic(String name){
log.info("저장 name={} --> nameStore={}", name, nameStore.get());
String localField = name;
nameStore.set(name); // 저장
sleep(1000);
log.info("조회 nameStore={}, localField={}", nameStore.get(), localField);
return nameStore.get(); // 저장한 데이터 꺼내기
// 값 제거 : nameStore.remove() - 해당 쓰레드가 쓰레드 로컬을 모두 사용하고 나면 ThreadLocal.remove() 로 로컬에 저장된 값을 제거해 주어야 함
}
ThreadLocal을 사용하여 메소드를 수정 후 다시 테스트 코드를 실행 해보면
본인의 Thread에 저장한 값이 알맞게 출력된다.
출처 : 인프런-스프링 핵심원리 고급편 강의