(7) ThreadLocal

@MinSang · August 17, 2024 · 6 min read

스레드로컬이 뭔가요?
스레드 로컬은 어떻게 구현되어 있나요? 단계별로 설명해보세요
1단계 구현
2단계 구현
3단계 구현
ThreadLocal.get()
정리
어디에 활용되나요?
스레드로컬 사용시 주의사항
Reference

스레드로컬이 뭔가요?

ThreadLocal은 멀티 스레드 환경에서 각각의 스레드가 별도의 변수를 저장하고 사용할 수 있습니다.

스레드 로컬은 어떻게 구현되어 있나요? 단계별로 설명해보세요

1단계 구현

ThreadLocal<T>를 구현하려고 할 때 Thread.currentThread()를 키로 사용하는 ConcurrentHashMap<Thread,T>으로 구현할 것 같습니다.

이는 뭔가 잘 작동될 것 같지만 문제가 있습니다.

  • ConcurrentHashMap은 동시성 문제를 해결하지만 그만큼 성능저하가 발생합니다.
  • Thread가 완료되어 GC가 가능한 후에도 ConcurrentHashMap이 해당 Thread 객체와 값인 객체에 대해 포인터를 유지하고 있으므로 GC에 의해 정리가 불가능합니다.

2단계 구현

이번에는 WeakReference를 사용하여 GC 문제를 해결하겠습니다.

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())

이제 다른 누군가 스레드를 참조하고 있지 않으면 Thread와 값인 객체는 가비지 수집이 될 수 있습니다. 하지만 여전히 스레드 경합 문제 때문에 성능 저하 문제를 해결할 수 없습니다.

3단계 구현

ThreadLocal을 구현할 때 스레드를 키로 둔다는 것은 올바른 생각이 아닐 수도 있습니다.

ThreadLocal 객체의 키를 Thread로 두는 대신, 각 스레드가 ThreadLocal 객체와 값을 매핑한 것을 저장하면 이전 구현의 문제를 해결할 수 있습니다.

new WeakHashMap<ThreadLocal,T>()
  • 각 스레드마다 각자의 Map을 들고 있기 때문에 더이상 동시성 문제가 발생하지 않습니다.

img

실제 Thread 클래스를 보면 다음과 같이 구현되어 있습니다.

ThreadLocal.ThreadLocalMap threadLocals = null;

앞서 말한 것처럼 Thread에 대해 ThreadLocal<T> 객체 - T 객체를 매핑하는 ThreadLocalMap을 소유하고 있습니다.

ThreadLocalMapWeakHashMap으로 구현되있지는 않지만, 약한 참조로 키를 보유하며, 동일한 매커니즘을 갖고 있습니다.

ThreadLocal.get()

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();
}

현재 수행중인 스레드의 ThreadLocalMap을 꺼내고 ThreadLocal 객체를 키로 넣어 값을 꺼내고 반환합니다.

정리

Java에서 ThreadLocal은 내부적으로 ThreadLocalMap이라는 내부 클래스를 사용하여 구현됩니다.

각 스레드는 Thread 객체 내부에 ThreadLocalMap을 가지고 있고, 이 맵은 ThreadLocal 객체를 key로 사용하여 값(value)을 저장합니다. 이를 통해 각 스레드는 자신만의 독립적인 값들을 저장하고 접근할 수 있습니다.

또한, ThreadLocal 클래스는 initialValue() 메서드를 제공하여 스레드가 해당 ThreadLocal에 처음 접근할 때 초기값을 설정할 수 있도록 합니다. 이를 통해 각 스레드가 최초로 값에 접근했을 때 초기화되는 값을 제공할 수 있습니다.

여기서 중요한 점은 ThreadLocalMap이 각 스레드의 내부에 저장되어 있기 때문에 동기화 문제가 발생하지 않고, 각 스레드가 독립적으로 값을 관리할 수 있다는 것입니다.

어디에 활용되나요?

스레드 로컬은 사용자 인증 정보, 트랜잭션 컨텍스트 등 스레드별로 독립적으로 유지해야 하는 정보를 저장하는 데 자주 사용됩니다.

예를 들어, 웹 애플리케이션에서 사용자의 요청을 처리하는 각 스레드가 사용자별 세션 정보를 유지해야 할 때 스레드 로컬을 사용할 수 있습니다.

또한, 데이터베이스 트랜잭션 관리에서 현재 트랜잭션의 상태를 스레드별로 관리해야 할 때도 스레드 로컬이 유용하게 사용됩니다.

스레드로컬 사용시 주의사항

스레드 풀을 사용하는 환경에서는 스레드 로컬을 사용할 때 스레드가 재사용되기 때문에, 스레드 로컬의 데이터가 의도치 않게 공유될 수 있습니다.

따라서 개발자가 명시적으로 remove() 메소드를 호출하여 데이터를 제거해야합니다.

Reference

https://stackoverflow.com/questions/1202444/how-is-javas-threadlocal-implemented-under-the-hood

https://d2.naver.com/helloworld/329631

@MinSang
지식과 경험을 기록하는 TIL 저장소