💡 개요
오늘은 스레드 내부에서 저장소로 사용되는 기술인 스레드 로컬에 대해 정리해 보자.
🛠️ 스레드 로컬
스레드 로컬이란, 각 스레드가 고유하게 가지는 저장소를 의미한다.
멀티 스레드 환경에서 공유를 피하고 싶은 데이터가 있을 경우, 이를 ThreadLocal을 통해 각 스레드에 독립적으로 저장할 수 있다.
보통 스레드는 데이터를 힙 영역이나 스레드 로컬 영역에 저장할 수 있는데, 힙 영역에 저장된 데이터는 모든 스레드가 접근할 수 있어 공유되고, 스레드 로컬에 저장된 데이터는 해당 스레드에서만 접근 가능하므로 동시성 문제나 데이터 정합성 문제를 방지할 수 있다.
public class ThreadLocalTest {
// 각 스레드마다 독립적인 값을 저장할 수 있는 ThreadLocal 변수
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
threadLocal.set(threadName + "의 데이터");
try {
Thread.sleep(100); // 약간의 지연
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " -> " + threadLocal.get());
};
Thread threadA = new Thread(task, "🧵Thread-A");
Thread threadB = new Thread(task, "🧵Thread-B");
threadA.start();
threadB.start();
}
}
[결과]
🧵Thread-B -> 🧵Thread-B의 데이터
🧵Thread-A -> 🧵Thread-A의 데이터
위 코드를 보면 ThreadLocal<String> threadLocal 은 모든 스레드가 공유하는 static 필드임에도 불구하고, 내부적으로는 각 스레드가 따로 값을 저장하므로, 스레드 A와 B는 서로의 데이터에 접근할 수 없다.
Spring MVC 구조에서는 일반적으로 요청이 Controller → Service → Repository 계층을 하나의 스레드에서 흐르게 된다.
이러한 구조 덕분에, 한 요청 안에서는 모든 계층에서 동일한 ThreadLocal 데이터를 조회할 수 있다.
이를 활용한 대표적인 예시가 데이터베이스에 접근할 때 사용되는 트랜잭션이다.
class MemberService {
private final MemberRepository memberRepository;
@Transactional
public void signUp(...) {
...
memberRepository.save(...);
}
}
위 코드를 보면 데이터베이스에 회원 정보를 저장하는 메서드에 @Transactional이 설정되어 있다.
이것의 의미는 Service 계층에서 트랜잭션을 시작하고, Repository 계층에서 DB 커넥션을 사용해야 하는 것이다.
그런데 트랜잭션을 시작했는데 Repository에서는 이 트랜잭션이 뭔지 모르면 어떻게 될까..?
트랜잭션이 제대로 작동하지 않고, 데이터 정합성에 문제 생길 수 있다.
Spring은 이러한 문제를 해결하기 위해, 트랜잭션이나 DB 커넥션 객체를 스레드 로컬에 저장한다.
1. 트랜잭션 A 시작
- → 현재 스레드에 해당 트랜잭션 관련 커넥션 객체를 ThreadLocal에 저장
2. 이후 Repository 계층에서 DB 접근
- → ThreadLocal에서 현재 스레드의 커넥션을 꺼내 재사용
3. 트랜잭션 A 종료
- → 커넥션 닫고, ThreadLocal.remove()로 정리
'개발 일기' 카테고리의 다른 글
[개발 일기] 2025.04.16 - 클래스가 final로 선언되면 내부 필드도 변경이 안될까? (0) | 2025.04.16 |
---|---|
[개발 일기] 2025.04.15 - 자바 vector (1) | 2025.04.15 |
[개발 일기] 2025.04.13 - CQRS (0) | 2025.04.13 |
[개발 일기] 2025.04.12 - 프로세스 vs 스레드 (0) | 2025.04.12 |
[개발 일기] 2025.04.11 - String vs StringBuffer vs StringBuilder (1) | 2025.04.11 |