ThreadLocal은 각각의 쓰레드 별로 별도의 저장공간을 제공하는 컨테이너 == 물건보관 창고

"각각 물건을 보관하면 각각 물건을 잘 보관할수 있게
겹치거나 사라지지 않게"
일반적으로 변수는 여러 스레드에 의해 공유될 수 있다. 만약 여러 스레드가 동시에 같은 변수를 읽고 쓰게 되면, 데이터가 꼬이거나 예상치 못한 오류가 발생할 수 있다. 이를 해결하기 위해 synchronized나 Lock 같은 동기화 메커니즘을 사용하기도 하지만, 이는 스레드가 순서를 기다려야 하므로 성능 저하를 일으킬 수 있다.
// 각 스레드마다 독립적인 사용자 이름을 저장할 ThreadLocal 변수
private static final ThreadLocal<String> userContext = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 스레드 풀 생성
ExecutorService executor = Executors.newFixedThreadPool(2);
// 첫 번째 작업: user1 로그인
executor.submit(() -> {
System.out.println("스레드 " + Thread.currentThread().getName() + " - user1 로그인 시작");
userContext.set("user1"); // 현재 스레드의 userContext에 "user1" 저장
printUserInfo();
// 작업이 끝나면 꼭 remove() 호출하여 메모리 누수 방지
userContext.remove();
System.out.println("스레드 " + Thread.currentThread().getName() + " - user1 로그아웃");
});
// 두 번째 작업: user2 로그인
executor.submit(() -> {
System.out.println("스레드 " + Thread.currentThread().getName() + " - user2 로그인 시작");
userContext.set("user2"); // 현재 스레드의 userContext에 "user2" 저장
printUserInfo();
userContext.remove();
System.out.println("스레드 " + Thread.currentThread().getName() + " - user2 로그아웃");
});
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
}
private static void printUserInfo() {
// 현재 스레드에 저장된 사용자 이름 가져오기
String username = userContext.get();
System.out.println("스레드 " + Thread.currentThread().getName() + " - 현재 로그인된 사용자: " + username);
}
}
결과
스레 pool-1-thread-1 - user1 로그인 시작
스레 pool-1-thread-2 - user2 로그인 시작
스레 pool-1-thread-1 - 현재 로그인된 사용자: user1
스레 pool-1-thread-2 - 현재 로그인된 사용자: user2
스레 pool-1-thread-1 - user1 로그아웃
스레 pool-1-thread-2 - user2 로그아웃
언제 사용?
하나의 작업 요청에 대한 모든 정보를 담는 '데이터 클래스'입니다. 마치 택배 송장처럼, 누가(username), 어떤 물건을(kollection, drama), 어디로(cineroomIds) 보내는지 같은 정보를 한 곳에 모아둡니다.
모든 로직에서 누가 했는지 기록을 남기기 위해 사용 (나의 경우 공통 entity를 만들고 어디서든 사용자 정보를 가져올 때 사용했다. )
만든 흐름은 이렇다.
요청 시작 >> 컨텍스트 설정 >> 데이터 사용 >> 데이터 종료
- 요청 시작 (Request Start)
- 사용자가 웹 애플리케이션에 HTTP 요청을 보냅니다. (예: 로그인, 게시글 작성 등)
- 웹 서버는 이 요청을 처리하기 위해 스레드 풀에서 스레드 하나를 가져와 할당합니다.
- 컨텍스트 설정 (Set Context)
- 애플리케이션의 필터(Filter) 또는 인터셉터(Interceptor) 같은 진입점에서 요청을 가로챕니다.
- 이 시점에 요청에서 필요한 정보(예: 로그인한 사용자 정보, 트랜잭션 ID, 요청 ID)를 추출하여 ThreadLocal 변수에 저장합니다.
- 코드 예시: StageContext.set(new StageRequest(username, ...))
- 데이터 사용 (Use Data)
- 이제 요청을 처리하는 스레드 안에서는 애플리케이션의 어느 계층(서비스, 리포지토리 등)에서든 ThreadLocal에 저장된 데이터를 쉽게 가져와 사용할 수 있습니다.
- 별도의 파라미터 전달 없이도 ThreadLocal.get() 메서드만 호출하면 되므로, 코드가 훨씬 간결해집니다.
- 코드 예시: StageRequest req = StageContext.get(); String user = req.getUsername();
- 컨텍스트 정리 (Clear Context)
- 요청 처리가 모두 끝나고 응답을 보내기 직전에, 다시 필터나 인터셉터가 동작합니다.
- 이때 ThreadLocal에 저장된 데이터를 **반드시 제거(remove)**해야 합니다.
- 코드 예시: StageContext.clear()
- 이 과정이 매우 중요한데, 만약 제거하지 않으면 스레드 풀에 반환된 스레드가 이전 요청의 데이터를 그대로 가지고 있게 되어, 다음 요청에서 예상치 못한 오류를 일으키거나 메모리 누수를 발생시킬 수 있습니다.
ThreadLocal 사용시 주의점
사용법은 간단하지만 꼭 주의해야할 점이 있다. ThreadLocalContext를 사용후에는 반납시 자동으로 값이 초기화되지 않기 때문에 Thread Pool 처럼 전체 Pool안에서 재활용하며 application이 구동될 경우 신규 context 생성 시 이전 쓰레기값이 채워진채로 재사용될 수가 있다.
'BACKEND' 카테고리의 다른 글
| [java]리플렉션 (reflection) (0) | 2025.08.22 |
|---|---|
| [java] Spring AOP(Aspect-Oriented Programming) (0) | 2025.08.22 |
| [Architecture]멀티테넌시(Multi-tenancy) -Shared Schema 방식 (0) | 2025.08.21 |
| [JAVA] toJson vs toPrettyJson (0) | 2025.08.19 |
| [DB]차집합 (LEFT OUTER JOIN + IS NULL) (0) | 2025.08.19 |