정의
ThreadLocal 이란 멀티 스레드 환경에서 각각의 스레드별로 저장 공간을 할당하여, 독립적인 값을 저장할 수 있게 해주는 기술 및 클래스를 말합니다.
사용 이유
Java 나 Spring의 멀티스레드 환경에서 어떤 클래스를 싱글톤으로 인스턴스를 하나 만들었다고 가정하겠습니다. (일반적인 Bean 도 예시가 될 수 있겠네요.)
이때 인스턴스의 필드나 static 공용 필드를 변경할 때 동기화 문제가 발생할 수 있습니다.
이는 여러 스레드에서 동시에 같은 필드(메모리 공간)를 접근하기 때문에 생기는 문제입니다.
이 문제를 가장 쉽게 해결하려면 각각의 공간을 만들고 각 스레드별로 자신의 저장 공간을 할당하면 됩니다
기본 개념
1. 독립성 : ThreadLocal 에 저장된 값은 해당 스레드에서만 접근이 가능하며 다른 스레드에서 참조할 수 없습니다. 따라서 멀티스레드 환경에서 동기화 문제를 해결할 수 있습니다.
2. 안정성 : 각 스레드는 ThreadLocal 객체에 대한 자신의 복사본을 가지고 있어서 데이터 변경 시 다른 스레드에 영향을 주거나 받지 않습니다.
위의 기본 개념을 생각하면서 예시 코드를 설명드리겠습니다.
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
public static void main(String[] args) {
Runnable task = () -> {
// 현재 스레드의 값을 설정
threadLocalValue.set((int) (Math.random() * 100));
System.out.println(Thread.currentThread().getName() + " - Value: " + threadLocalValue.get());
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
Integer 타입을 저장할 수 있는 ThreadLocal 의 인스턴스를 생성하는 방법은 아래와 같습니다
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
또한 ThreadLocal 에서 제공하는 get, set 메서드를 통해 데이터를 저장하고 가져올 수 있습니다.
예시로 ThreadLocal 에서 정수 30을 저장하고 가져오는 코드는 아래와 같습니다
threadLocalValue.set(30);
Integer value = threadLocalValue.get(); // 30
ThreadLocal에 저장된 데이터를 제거하는 방법은 아래와 같습니다
threadLocalValue.remove();
ThreadLocal 클래스의 내부 구현을 보면 조금 더 쉽게 원리에 대해 이해할 수 있습니다.
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
Java의 Thread 객체는 ThreadLocal의 ThreadLocalMap 을 필드로 가지고 있습니다.
즉 아래와 같이 ThreadLocal 을 Key로 하는 Map을 통해 데이터를 저장하고 있습니다.
public class ThreadLocal<T> {
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
주의 사항
1. 사용 후 명시적 삭제 : ThreadLocal 은 스레드가 종료되더라도 저장된 값이 남아 있을 수도 있습니다. 따라서 스레드 풀을 사용하는 경우 명시적으로 데이터를 제거(remove)하는 것이 좋습니다.
2. 적절한 사용 : ThreadLoca 은 필요한 경우에만 사용하는 것이 좋습니다. 많은 스레드가 동일안 메모리 공간을 공유해야 되는 경우 다른 동기화 방법을 생각해야 됩니다
3. 성능 저하 : ThreadLocal 은 메모리 사용을 증가시킵니다. 사용 빈도와 데이터 양에 따라 성능에 영향을 줄 수 있습니다.
주의사항 1번에 대해서 풀어서 설명드리겠습니다.
스레드 풀이 아닌 일반적인 경우 매 요청마다 스레드가 생성되고 응답이 끝나면 스레드가 소멸됩니다.
이 경우는 ThreadLocal에 저장된 데이터를 접근할 수 없기 때문에 문제가 되지 않습니다.
반면 스레드풀을 사용하는 경우 응답을 마친 스레드는 다시 풀에 들어가 재사용됩니다.
스레드가 소멸되지 않기 때문에 스레드가 참조하고 있는 ThreadLocal 값 또한 GC에 의해 자동으로 제거되지 않습니다.
그래서 ThreadLocal을 다 사용하고 나면 remove() 메서드로 다른 스레드가 사용할 수 없도록 명시적으로 제거해야 합니다.
마무리
ThreadLocal 이란 멀티 스레드 환경에서 동시성 문제를 해결할 수 있는 기술 중 하나입니다.
각각의 스레드별로 저장 공간을 할당하여, 독립적인 값을 저장할 수 있어서 상황에 맞게 사용한다면 편하고 안전한 시스템을 개발할 수 있습니다.
'Java' 카테고리의 다른 글
[Java] 빈 문자열 체크 방법 정리(isEmpty, isBlank, equals("") 차이점) (0) | 2024.10.31 |
---|---|
[Java] 문자열을 대소문자 구분 없이 비교하는 방법, equalsIgnoreCase()와 compareToIgnoreCase() 차이점 (1) | 2024.10.28 |
[Java] 스트림(Stream) (0) | 2023.01.30 |
[Java] String 문자열 비교할 때 equals() 와 contentEquals() 차이점 (0) | 2023.01.03 |
[Java] 현재 실행중인 클래스명, 메소드명 알아내기 (0) | 2022.12.29 |
댓글