티스토리 뷰
Overview
ThreadLocal을 사용하던 중 ThreadLocal은 반드시 remove를 해주어야 하고, 그렇지 않으면 다른 스레드간 데이터 간섭이 생겨 에러가 발생할 수 있다는 점을 어쩌면 당연시하고 사용했습니다. 하지만 Spring을 사용하다보면 Thread Pool은 스레드를 작업이 끝난 후 삭제하지 않고 다시 반환한다는 점에서 그렇다면 '반환된 Thread간 간섭은 없는걸까?' 라는 의문이 들었습니다. 지금 생각하면 조금 바보같은 생각일지 몰라도 단순히 저 궁금증에서 시작하게 되었습니다.
ThreadLocal 저장 방법
위 질문을 해결하기 위한 가장 기본적인 접근법은 Heap과 Stack중 어느 곳에 저장되는 지를 확인하는 것입니다.
그리고 ThreadLocal은 remove를 사용해야 한다는 점에서 간단하게 Heap에 저장된다는 것을 유추 할 수 있습니다.
각 Thread는 ThreadLocalMap을 통해 변수들을 관리하는데, Thread Pool에서 Thread를 반납하지 않기 때문에
ThreadLocalMap은 remove 해주지 않는 한 그 데이터들이 GC에 의해 삭제되지 않습니다.
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
위처럼 Thread에서는 각각 ThreadLocalMap을 가지고 있으며,
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocal에서 set()을 할때 생성해주게 됩니다.
그럼 해당 Thread의 ThreadLocal은 데이터를 Thread가 삭제되기 전까지는
ThreadLocalMap은 GC에 관리 대상이 아니고 데이터가 그대로 유지됩니다.
여기서 우리는 ThreadLocal은 왜 remove를 해줘야하는지 알 수 있습니다.
Thread Pool에서의 Thread
Thread에 대해서 정의해볼 필요가 있습니다.
우선 스레드는 작업 실행을 담당하는 실행 단위이자, 독립적인 실행 컨텍스트를 가진 가상 프로세서입니다.
위 정의에서 볼 수 있듯 작업을 실행하는 담당자 입니다.
그래서 스레드가 실행하는 각 작업은 Stack 영역에 저장되어 실행되고,
해당 스레드의 작업이 끝난다면 모두 사라지게 됩니다.
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello from MyThread");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 새로운 OS 스레드 생성
}
}
예를 들어 위 코드에서 run 내에 담긴 작업들은 Stack에 저장되고, thread가 작업을 종료한 시점에서
GC 대상이 되어 run에 있던 작업들은 지워집니다.
Thread Pool도 단지 동일한 Thread를 생성하여 미리 준비해두는 개념입니다.그렇다면 Thread가 특정 작업을 수행할 때에는 일반 Thread와 마찬가지로 call stack을 Stack 메모리에 저장하게 됩니다.그리고 작업시 수행됐던 call stack들은 GC의 대상이 되어 제거됩니다.
그럼 Thread Pool말고 새로 생성한 Thread에서는 ThreadLocal은 remove를 안해도 될까요?
정답은 '네' 입니다.
public class ThreadLocalExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
threadLocal.set("Hello, World!"); // ThreadLocal에 데이터 저장
System.out.println(Thread.currentThread().getName() + " -> " + threadLocal.get());
});
thread.start();
}
}
위 코드처럼 새로 생성한 Thread에서는 threadLocal.remove()를 하지 않아도 괜찮다.
thread.start()가 종료되는 시점에서 Thread는 GC 대상이 되기 때문이다.
결론
사실 Thread와 ThreadLocal의 개념과 내부동작만 알고 있었더라면 쉽게 알 수 있는 내용들입니다. 하지만 이전에는 겉으로 개념을 이해하고 ThreadLocal은 Thread라서 그냥 자원이 공유되니까 삭제해줘야하는구나 하고 넘어갔지만 우연히 든 질문에 더 정확히 알게된 좋은 기회였습니다.
'Back End > Spring' 카테고리의 다른 글
Spring Caching으로 성능 올리기 - (3) cache config(ehcache, redis, caffeine) (0) | 2025.01.11 |
---|---|
Spring Caching으로 성능 올리기 - (2) key generator (0) | 2025.01.11 |
Spring Caching으로 성능 올리기 - (1) 기본 사용법 (0) | 2024.12.01 |
[Test] Spring boot, Junit, Testcontainers를 이용한 테스트 환경 만들기 (0) | 2024.04.11 |
[Spring Data] Spring Data MongoDB - insert() vs save() (0) | 2024.04.06 |
- Total
- Today
- Yesterday
- consumer
- centos
- API
- Container
- Java
- K8S
- React
- Linux
- Firebase
- spring
- Front
- OS
- frontend
- Producer
- backend
- zookeeper
- JPA
- apache kafka
- 프론트엔드
- rhel
- KAFKA
- cs
- Data Engineering
- feign client
- NextJS
- apache
- broker
- 리액트
- spring boot
- caching
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |