본문 바로가기

스프링

[Spring] 동시성 문제 - ThreadLocal로 개선

참고 1. 스프링-핵심-원리-고급편, 김영한

동시성 문제

여러 쓰레드가 동시에 같은 인스턴스의 필드 값을 변경하면서 발생하는 문제

동시성 문제가 발생하는 상황

  • 지역 변수에서는 발생하지 않음. (지역 변수는 쓰레드마다 각각 다른 메모리 영역이 할당)
  • 인스턴스의 필드, static 같은 공용 필드에 접근할 때 발생
  • 주로 싱글톤에서 자주 발생

쓰레드 로컬, Thread Local

해당하는 쓰레드만 접근할 수 있는 특별한 저장소이다. 만약, "A" 쓰레드와 "B" 쓰레드가 있다면, A와 B 각각의 전용 저장소가 만들어 진다. 이후, A와 B가 데이터를 변경할 경우, 쓰레드 로컬이 각각의 전용 저장소에 연결해준다. 이를 통해 동시성 문제를 해결할 수 있다.

예시

동시성 문제가 발생하는 변수를 ThreadLocal로 감싸준다.

//동시성 문제가 발생하는 변수
private String store;

//ThreadLocal로 동시성 문제 개선
private ThreadLocal<String> store = new ThreadLocal<>();

함수

  • 값 저장: ThreadLocal.set(value)
  • 값 조회: ThreadLocal.get()
  • 값 삭제: ThreadLocal.remove()

주의사항

쓰레드가 ThreadLocal을 모두 사용하고 나면, 반드시 ThreadLocal.remove()로 ThreadLocal에 저장된 값을 제거해야 한다. 

  • 왜 제거해야 할까?
    • 쓰레드의 생성에는 많은 비용이 발생하기 때문에 WAS(톰캣)은 쓰레드를 미리 생성하고 쓰레드 풀에 보관한다. 사용자의 요청에 따라 쓰레드를 꺼내서 사용하고, 다시 쓰레드 풀에 반납한다.
    • 이때, 쓰레드 풀의 1번 쓰레드를 첫 사용자의 요청에 사용되면, 이 쓰레드의 전용 ThreadLocal 저장소가 할당된다. 
    • 이후, 첫 사용자의 요청-응답 과정이 끝나면, 쓰레드는 쓰레드 풀에 반납된다. 즉, 쓰레드는 삭제가 되지 않으며, 전용 ThreadLocal 저장소 또한 유지가 된다.
    • 이때, 두 번째 사용자의 요청이 들어오면 반납된 1번 쓰레드가 재사용될 수 있으며, 해당 ThreadLocal 저장소도 그대로 불러온다. 첫 사용자의 요청-응답 이후 ThreadLocal을 제거하지 않았다면, 데이터가 남아있게 된다.
    • 꼭, 쓰레드의 반납 과정에서 ThreadLocal도 반드시 제거해주자