Programing

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

lottogame 2020. 10. 24. 09:22
반응형

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


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


여기에있는 모든 답변은 정확하지만 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 에서 가져온 코드 스 니펫 .


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


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


을 구현한다고 가정 해 보겠습니다. 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스레드에 대해 여러 변수를 원하면 어떻게 합니까? 가장 간단한 방법은 만드는 것입니다 threadLocalsHashMap, key각 항목의는의 이름입니다 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);
    }

    ...

}

The core part of the ThreadLocalMap class is the Entry class, which extends WeakReference. It ensures that if the current thread exits, it will be garbage collected automatically. This is why it uses ThreadLocalMap instead of a simple HashMap. It passes the current ThreadLocal and its value as the parameter of the Entry class, so when we want to get the value, we could get it from table, which is an instance of the Entry class:

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

This is what is like in the whole picture:

The Whole Picture


Conceptually, you can think of a ThreadLocal<T> as holding a Map<Thread,T> that stores the thread-specific values, though this is not how it is actually implemented.

The thread-specific values are stored in the Thread object itself; when the thread terminates, the thread-specific values can be garbage collected.

Reference : JCIP

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

반응형