Spring 웹 애플리케이션은 요청마다 새로운 스레드를 생성하여 처리합니다.
이 멀티스레드 환경에서 각 요청이 독립적이어야 하지만, 공용 데이터에 동시에 접근하면 동시성 문제가 발생할 수 있습니다.
ThreadLocal은 이러한 문제를 해결하는 데 유용한 도구로, 각 스레드에 독립된 저장 공간을 제공하여 안전하게 데이터를 관리할 수 있게 합니다. 이번 글에서는 ThreadLocal의 개념과 Spring 프레임워크에서의 활용 방법을 살펴보겠습니다.😽
ThreadLocal이란?
📝 `ThreadLocal`은 각각의 스레드가 독립적으로 변수 값을 저장할 수 있도록 하는 클래스
멀티스레드 환경에서는 여러 스레드가 하나의 자원을 동시에 접근하는 경우가 많은데 이때 발생하는 동시성 문제를 해결하기 위해 각 스레드에 고유한 변수를 할당할 수 있도록 도와줍니다.
간단히 말해, 스레드마다 별도의 변수 복사본을 가지고 작업하도록 하는 개념입니다.
웹 애플리케이션에서 각 사용자의 요청을 별개의 스레드로 처리할 때 특정 스레드에만 접근 가능한 데이터를 저장하고 싶은 경우에 유용합니다.
ThreadLocal의 동작 원리
`ThreadLocal`의 작동 원리를 이해하기 위해 `ThreadLocal`의 핵심 메서드인 `get()`과 `set()`을 살펴보겠습니다.
먼저 `ThreadLocal` 변수를 설정할 때는 set() 메서드를 사용합니다.
`set()` 메서드는 현재 스레드에서만 접근 가능한 데이터를 `ThreadLocal`에 저장합니다.

- `Thread.currentThread()`를 호출하여 현재 스레드를 가져옵니다.
- `getMap()` 메서드를 통해 현재 스레드의 `ThreadLocalMap` 객체를 가져옵니다.
이 `ThreadLocalMap`은 해당 스레드에 저장된 모든 `ThreadLocal` 데이터들을 관리합니다. - 만약 `ThreadLocalMap`이 없다면 `createMap()`을 호출해 새로운 Map을 생성합니다.
`ThreadLocalMap`은 각 스레드가 자기만의 데이터 저장소를 갖도록 하는데, 이 구조 덕분에 다른 스레드와 데이터가 공유되지 않습니다.
`get()`메서드는 현재 스레드의 `ThreadLocalMap`에서 데이터를 가져옵니다.

- 현재 스레드의 `ThreadLocalMap`에서 값을 조회하며 만약 값이 없으면 `setInitialValue()`를 호출해서 초기값을 설정합니다.
이로 인해 각 스레드는 독립적으로 값을 저장하고 읽을 수 있게 됩니다.
ThreadLocal의 활용 예시
웹 애플리케이션에서 사용자의 인증 정보나 트랜잭션 정보와 같은 데이터를 각 스레드마다 독립적으로 저장해야 할 때 유용합니다.
예를 들어, 트랜잭션 정보를 `ThreadLocal`을 통해 저장하고 읽어오는 코드의 예는 다음과 같습니다.
public class TransactionManager {
private static final ThreadLocal<Transaction> transactionHolder = new ThreadLocal<>();
public static void startTransaction() {
transactionHolder.set(new Transaction()); // 각 스레드에 개별 트랜잭션 저장
}
public static Transaction getTransaction() {
return transactionHolder.get(); // 현재 스레드의 트랜잭션 가져오기
}
public static void endTransaction() {
transactionHolder.remove(); // 현재 스레드에 트랜잭션 제거
}
}
이 코드를 통해 각 요청(스레드)은 별도의 트랜잭션을 유지하게 됩니다. 이는 트랜잭션이 종료될 때까지 다른 스레드에서 접근할 수 없습니다.
ThreadLocalMap의 내부 구조
ThreadLocalMap은 ThreadLocal 객체를 키로 사용해 데이터 저장소를 관리합니다. 이 구조를 통해 스레드가 종료될 때까지 ThreadLocal 변수에 저장된 값이 유지되며, 다른 스레드에서는 이 값에 접근할 수 없습니다.
ThreadLocalMap에서 중요한 점은 메모리 누수 문제인데, 스레드가 종료되지 않으면 ThreadLocalMap의 데이터가 계속해서 메모리를 점유할 수 있습니다. 이를 방지하기 위해 remove() 메서드를 사용해 데이터를 명시적으로 제거해야 합니다.
Spring에서의 ThreadLocal활용
Spring 프레임워크는 `ThreadLocal`을 활용하여 `@Transaction`이나 `@RequestScope` 같은 어노테이션이 내부적으로 어떻게 동작하는지 설명할 수 있습니다.
@Transactional
- 이 어노테이션을 사용하면 트랜잭션이 시작됩니다.
- Spring은 트랜잭션 정보를 ThreadLocal에 저장합니다.
이로 인해 현재 요청과 관련된 트랜잭션을 안전하게 관리할 수 있습니다. - 예를 들어, 여러 요청이 동시에 처리될 때 각 요청은 자신의 트랜잭션 정보를 가지고 있기 때문에 서로의 트랜잭션에 영향을 주지 않습니다.
@RequestScope
- 이 어노테이션은 HTTP 요청에 대한 데이터를 저장하는 데 사용됩니다.
- `ThreadLocal`을 활용하여 각 요청이 끝날 때까지 데이터를 유지할 수 있습니다.
- 즉, 사용자가 요청을 보낼 때 생성된 데이터는 그 요청이 처리되는 동안만 존재하고 요청이 끝나면 자동으로 사라집니다.
💡 ThreadLocal을 통해 Spring에서는 요청마다 독립적인 데이터 관리를 할 수 있습니다. 멀티스레드 환경에서 발생할 수 있는 데이터 충돌이나 동시성 문제를 효과적으로 해결할 수 있습니다.
'Backend > Spring' 카테고리의 다른 글
| [Spring] 프록시 패턴(Proxy Pattern) (1) | 2024.11.09 |
|---|---|
| [Spring] @Async로 비동기 작업 최적화하기 (0) | 2024.11.08 |
| [Spring] @EventListener VS @TransactionEventListener (0) | 2024.10.26 |
| [Spring] 스프링에서 이벤트 발행과 구독 @EventListener (0) | 2024.10.25 |
| [Spring] 트랜잭션 상태에 맞춘 이벤트 처리 @TransactionalEventListener (0) | 2024.10.25 |