HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
📝
남득윤 학습 저장소
/
🧵
멀티쓰레드, 동시성 프로그래밍
/
🧵
ThreadLocal
🧵

ThreadLocal

@ 참고)
  • 김영한 - 스프링 핵심 원리 - 고급편
  • 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()를 호출 할 수 있고 유일성이 보장됩니다.
 
notion image
 

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.initializeStrategy 일부 발췌
 
위 코드에서 SecurityContextHolder 는 전략을 초기화 합니다. 이중 쓰레드마다 SecurityContext 를 가지는 MODE_THREADLOCAL 과 해당 쓰레드 및 자식 쓰레드마다 SecurityContext 를 가지는 MODE_INHERITABLETHREADLOCAL 은 전략객체 내부에 ThreadLocal<SecurityContext> 필드를 가집니다.
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
notion image