Java의 ThreadLocal은 어떻게 구현됩니까?


81

ThreadLocal은 어떻게 구현됩니까? Java로 구현됩니까 (ThreadID에서 객체로의 일부 동시 맵 사용), 아니면 좀 더 효율적으로 수행하기 위해 JVM 후크를 사용합니까?

답변:


120

여기에있는 모든 대답은 정확하지만 ThreadLocal의 구현이 얼마나 영리한 지에 대해 다소 광택이 있기 때문에 약간 실망 스럽습니다 . 나는 단지 소스 코드를ThreadLocal 보고 있었고 그것이 어떻게 구현되었는지에 대해 기분 좋은 인상을 받았다.

순진한 구현

ThreadLocal<T>javadoc에 설명 된 API에 따라 클래스 를 구현하도록 요청 했다면 어떻게 하시겠습니까? 초기 구현은 키로 ConcurrentHashMap<Thread,T>사용 하는 것 Thread.currentThread()입니다. 이것은 합리적으로 잘 작동하지만 몇 가지 단점이 있습니다.

  • 스레드 경합- ConcurrentHashMap꽤 똑똑한 클래스이지만 궁극적으로 여러 스레드가 어떤 식 으로든 문제를 일으키지 않도록 방지해야하며 다른 스레드가 정기적으로 충돌하면 속도가 저하됩니다.
  • 스레드가 완료되고 GC 될 수있는 후에도 스레드와 객체 모두에 대한 포인터를 영구적으로 유지합니다.

GC 친화적 인 구현

좋아, 다시 시도하고 약한 참조 를 사용하여 가비지 수집 문제를 처리해 보겠습니다 . WeakReference를 다루는 것은 혼란 스러울 수 있지만 다음과 같이 빌드 된 맵을 사용하는 것으로 충분해야합니다.

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

또는 우리가 구아바를 사용하고 있다면 (그렇게해야합니다!) :

new MapMaker().weakKeys().makeMap()

즉, 다른 사람이 스레드를 붙 잡지 않으면 (완료됨을 의미) 키 / 값이 가비지 수집 될 수 있습니다. 이는 개선 사항이지만 여전히 스레드 경합 문제를 해결 ThreadLocal하지 못합니다. 놀라운 수업입니다. 또한 누군가가 Thread완료된 후 객체 를 붙잡기로 결정하면 GC되지 않으므로 현재 기술적으로 도달 할 수 없더라도 우리 객체도 마찬가지입니다.

영리한 구현

우리는 ThreadLocal스레드를 값에 매핑하는 것으로 생각하고 있지만 실제로 생각하는 올바른 방법은 아닐 수 있습니다. Threads에서 각 ThreadLocal 객체의 값으로의 매핑이라고 생각하는 대신 ThreadLocal 객체를 각 Thread의 매핑하는 것으로 생각하면 어떨까요? 각 스레드가 매핑을 저장하고 ThreadLocal이 해당 매핑에 멋진 인터페이스를 제공하는 경우 이전 구현의 모든 문제를 피할 수 있습니다.

구현은 다음과 같습니다.

// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()

단 하나의 스레드 만이 맵에 액세스하기 때문에 여기서 동시성에 대해 걱정할 필요가 없습니다.

Java 개발자는 여기에서 우리보다 큰 이점이 있습니다. Thread 클래스를 직접 개발하고 여기에 필드와 작업을 추가 할 수 있습니다. 이것이 바로 그들이 한 일입니다.

에서 java.lang.Thread다음 줄이있다 :

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

주석에서 알 수 있듯이 실제로 ThreadLocal객체 가 추적하는 모든 값의 패키지 전용 매핑입니다 Thread. 구현은 ThreadLocalMap하지 않은 것이다 WeakHashMap하지만 약한 참조하여 지주 키를 포함하는 동일한 기본 계약을 따른다.

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

그리고 ThreadLocal.setInitialValue()이렇게 :

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

기본적 으로이 스레드 의 맵 사용하여 모든 ThreadLocal객체 를 보관하십시오 . 이렇게하면 다른 스레드의 값 ( ThreadLocal말 그대로 현재 스레드의 값에만 액세스 할 수 있음) 에 대해 걱정할 필요 가 없으므로 동시성 문제가 없습니다. 또한 Thread이 작업이 완료되면 해당 맵이 자동으로 GC 처리되고 모든 로컬 개체가 정리됩니다. Thread가 붙잡혀 있어도 ThreadLocal개체는 약한 참조로 유지되며 ThreadLocal개체가 범위를 벗어나는 즉시 정리할 수 있습니다 .


말할 필요도없이,이 구현에 다소 감명을 받았으며, 많은 동시성 문제를 상당히 우아하게 극복하고 (핵심 Java의 일부가되는 것을 이용하여 인정했지만, 그렇게 영리한 클래스이기 때문에 용서할 수 있습니다) 빠르고 한 번에 하나의 스레드에서만 액세스하면되는 객체에 대한 스레드로부터 안전한 액세스.

tl; dr ThreadLocal 의 구현은 매우 멋지고, 언뜻 생각하는 것보다 훨씬 빠르고 / 스마트합니다.

이 대답을 좋아한다면 당신은 또한 내 (보다 자세한) 감사 수도 의 토론을ThreadLocalRandom .

Thread/ Oracle / OpenJDK의 Java 8 구현ThreadLocal 에서 가져온 코드 스 니펫 .


1
귀하의 답변은 멋져 보이지만 지금은 너무 깁니다. +1하고 수락했으며 나중에 읽을 수 있도록 getpocket.com 계정에 추가하겠습니다. 감사!
ripper234 2013 년

map.values ​​()와 같이 전체 값 목록에 액세스 할 수있는 ThreadLocal과 같은 것이 필요합니다. 따라서 내 순진한 구현은 WeakHashMap <String, Object> 키가 Thread.currentThread (). getName ()입니다. 이것은 스레드 자체에 대한 참조를 피합니다. 쓰레드가 사라지면 더 이상 쓰레드의 이름을 유지하는 것이 없으며 (가정, 인정합니다) 내 가치는 사라질 것입니다.
bmauter 2013 년

나는 실제로 최근에 그 효과에 대한 질문에 답했다 . A WeakHashMap<String,T>는 몇 가지 문제를 소개하고 스레드로부터 안전하지 않습니다. "== 연산자를 사용하여 객체 ID를 테스트하는 메소드가 동일한 키 객체와 함께 사용하기위한 것입니다"-실제로 Thread객체를 키로 사용하는 것이 더 좋습니다. 사용 사례에 대해 위에서 설명한 Guava weakKeys 맵을 사용하는 것이 좋습니다.
dimo414 2013 년

1
약한 키는 필요하지 않지만 동기화 된 HashMap을 통해 ConcurrentHashMap을 사용하는 것이 좋습니다. 전자는 다중 스레드 액세스를 위해 설계되었으며 각 스레드가 일반적으로 다른 키에 액세스하는 경우 훨씬 더 잘 작동합니다.
dimo414 2013 년

1
@shmosel이 클래스는 고도로 조정되었으므로 이러한 고려가 이루어 졌다는 가정에서 시작합니다. 한눈에 스레드가 정상적으로 종료 될 때가 Thread.exit()호출되고 threadLocals = null;바로 볼 수 있음을 알 수 있습니다. 이 버그 를 언급하는 주석은 여러분이 읽을 수도 있습니다.
dimo414

33

당신은 의미 java.lang.ThreadLocal합니다. 매우 간단합니다. 실제로 각 Thread객체 내에 저장된 이름-값 쌍의 맵입니다 ( Thread.threadLocals필드 참조 ). API는 구현 세부 사항을 숨기지 만 그게 전부입니다.


정의에 따라 데이터가 단일 스레드에만 표시된다는 점을 감안할 때 왜 필요한지 알 수 없습니다.
skaffman

8
맞습니다. ThreadLocalMap은 스레드 내에서만 액세스되므로 ThreadLocalMap 주변이나 내부에는 동기화 또는 잠금이 없습니다.
Cowan

8

Java의 ThreadLocal 변수는 Thread.currentThread () 인스턴스가 보유한 HashMap에 액세스하여 작동합니다.


그것은 정확하지 않습니다 (또는 적어도 더 이상 아닙니다). Thread.currentThread ()는 Thread.class의 기본 호출입니다. 또한 Thread에는 해시 코드의 단일 버킷 (배열) 인 "ThreadLocalMap"이 있습니다. 이 개체는지도 인터페이스를 지원하지 않습니다.
user924272

1
그것은 기본적으로 내가 말한 것입니다. currentThread ()는 ThreadLocals를 값에 매핑하는 Thread 인스턴스를 반환합니다.
Chris Vest

4

을 구현한다고 가정 해 보겠습니다. ThreadLocal스레드별로 어떻게 만들까요? 물론 가장 간단한 방법은 Thread 클래스에 비 정적 필드를 만드는 것 threadLocals입니다. 각 스레드는 스레드 인스턴스로 표시되기 때문에 threadLocals모든 스레드에서도 다를 수 있습니다. 그리고 이것은 또한 Java가하는 일입니다.

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

여기는 무엇입니까 ThreadLocal.ThreadLocalMap? threadLocals스레드에 대한 만 있기 때문에 단순히 스레드 threadLocals로 간주 하면 ThreadLocal(예 : threadLocals를로 정의 Integer) ThreadLocal특정 스레드에 대해 하나만 갖게됩니다 . ThreadLocal스레드에 대해 여러 변수를 원하면 어떻게 합니까? 가장 간단한 방법은 만드는 것입니다 의를threadLocalsHashMapkey 각 항목의는의 이름입니다 ThreadLocal변수, 그리고 value각 항목의는의 값이 ThreadLocal변수입니다. 좀 헷갈 리나요? 두 개의 스레드 t1t2. 생성자 Runnable의 매개 변수 와 동일한 인스턴스를 취하고 Thread둘 다 및 ThreadLocal라는 두 개의 변수를 갖습니다 . 이게 어떤지.tlAtlb

t1.tlA

+-----+-------+
| Key | Value |
+-----+-------+
| tlA |     0 |
| tlB |     1 |
+-----+-------+

t2.tlB

+-----+-------+
| Key | Value |
+-----+-------+
| tlA |     2 |
| tlB |     3 |
+-----+-------+

값은 내가 구성한 것입니다.

이제 완벽 해 보입니다. 그러나 무엇입니까ThreadLocal.ThreadLocalMap 입니까? 왜 그냥 사용하지 않았 HashMap습니까? 문제를 해결하기 위해 클래스 의 set(T value)메소드를 통해 값을 설정하면 어떻게되는지 살펴 보겠습니다 ThreadLocal.

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

getMap(t) 단순히 반환 t.threadLocals . 때문에이 t.threadLocals에 initilized 된 null우리가 입력 할 수 있도록, createMap(t, value)첫째 :

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

그것은 새로운 창조 ThreadLocalMap현재 ThreadLocal인스턴스와 설정할 값을 사용하여 인스턴스를 . 의 어떤 보자 ThreadLocalMap는의 사실 부분에있어,처럼 ThreadLocal클래스

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    ...

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    ...

}

ThreadLocalMap클래스 의 핵심 부분 Entry classWeakReference. 현재 스레드가 종료되면 자동으로 가비지 수집됩니다. 이것이 ThreadLocalMap단순한 HashMap. 그것은 현재를 통과ThreadLocal 값과 해당 값을 Entry클래스 의 매개 변수로 하므로 값을 얻으려면 클래스 table의 인스턴스 인 에서 가져올 수 있습니다 Entry.

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

이것은 전체 그림에서와 같습니다.

전체 그림


-1

개념적으로 는 스레드 특정 값을 저장하는 a ThreadLocal<T>를 보유하는 Map<Thread,T>것으로 생각할 수 있지만 실제로 구현되는 방법은 아닙니다.

스레드 별 값은 Thread 개체 자체에 저장됩니다. 스레드가 종료되면 스레드 별 값이 가비지 수집 될 수 있습니다.

참조 : JCIP


1
개념적으로는 그렇습니다. 그러나 위의 다른 답변에서 볼 수 있듯이 구현은 완전히 다릅니다.
Archit
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.