@ 참고)
- 김영한 - 스프링 핵심 원리 - 고급편
- oracle - ThreadLocal
- 쓰레드 로컬 은 해당 쓰레드만 접근 할 수 있는 특별한 저장소를 말한다.
- 자바는 쓰레드 로컬을 지원하기 위한
java.lang.ThreadLocal
클래스를 제공한다.
- 예를 들어 아래
ThreadId
클래스는 현재 쓰레드의 unique 한 id 값을 생산합니다.
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
쓰레드별로
ThreadLocal
필드인 threadId
의 초기화는 단 한번만 이루어지기 때문에 한 쓰레드당 한번만 nextId
의 getAndIncrement()
를 호출 할 수 있고 유일성이 보장됩니다.
ThreadLocal 사용법
- 값 저장:
ThreadLocal.set(xxx)
- 값 조회:
ThreadLocal.get()
- 값 제거:
ThreadLocal.remove()
ThreadLocal 조심해야할 점
⚠️ we should be extra careful when we're using ThreadLocals and thread pools together.
ThreadLocal을 도입하면 동시성 이슈를 해결할 수 있다는 장점이 있지만 조심하지 않으면 메모리 누수를 일으켜 큰 장애를 야기할 수 있다. 톰캣 같은 WAS의 경우 Thread를 새로 생성하는데 비용이 크기 때문에 자체적으로 ThreadPool을 가지고 있으면서 Thread를 재사용함
이때 하나의 작업 요청이 들어와 Thread-1이 할당되었다가 작업을 마치고 Thread-1이 다시 ThreadPool로 반환되었다고 가정반환될 때 Thread-1 내 ThreadLocal 초기화를 하지 않을 경우 Thread-1 전용 보관소 데이터가 그대로 남아있음앞서 말한 것처럼 ThreadPool의 목적은 Thread를 새로 생성하지 않고 재활용하는 것이므로 다른 작업 요청이 들어올 때 전용 보관소가 초기화되지 않은 Thread-1이 다시 할당될 수 있다.
이럴 경우 클라이언트는 이전 사용자가 요청한 작업 내용을 조회하는 상황이 발생할 수도 있음 (엄청난 장애)따라서, ThreadLocal은 Thread가 반환될 때 remove 메서드를 통해 반드시 초기화가 되어야 함
구현한 로직의 마지막에 초기화를 진행하거나WAS에 반환될 때 인터셉터 혹은 필터 단에서 초기화하는 방법으로 진행
Spring Security 에서 사용하는 코드
Spring Security 에서 사용자의 인증 정보(
Authentication
)는 SecurityContext
에 담아 보관합니다.SecurityContext
는 SecurityContextHolder
에 의해 관리되는데 이는 SecurityContext
를 공유하는 범위에대한 정책을 커스텀하게 가져가기 위해서입니다.
SecurityContextHolder.initializeStrategy
일부 발췌위 코드에서
SecurityContextHolder
는 전략을 초기화 합니다. 이중 쓰레드마다 SecurityContext
를 가지는 MODE_THREADLOCAL
과 해당 쓰레드 및 자식 쓰레드마다 SecurityContext
를 가지는 MODE_INHERITABLETHREADLOCAL
은 전략객체 내부에 ThreadLocal<SecurityContext>
필드를 가집니다.
private static final ThreadLocal<SecurityContext>
contextHolder
= new ThreadLocal<>();
