ThreadLocal은 어떻게 구현됩니까? Java로 구현됩니까 (ThreadID에서 객체로의 일부 동시 맵 사용), 아니면 좀 더 효율적으로 수행하기 위해 JVM 후크를 사용합니까?
답변:
여기에있는 모든 대답은 정확하지만 ThreadLocal
의 구현이 얼마나 영리한 지에 대해 다소 광택이 있기 때문에 약간 실망 스럽습니다 . 나는 단지 소스 코드를ThreadLocal
보고 있었고 그것이 어떻게 구현되었는지에 대해 기분 좋은 인상을 받았다.
순진한 구현
ThreadLocal<T>
javadoc에 설명 된 API에 따라 클래스 를 구현하도록 요청 했다면 어떻게 하시겠습니까? 초기 구현은 키로 ConcurrentHashMap<Thread,T>
사용 하는 것 Thread.currentThread()
입니다. 이것은 합리적으로 잘 작동하지만 몇 가지 단점이 있습니다.
ConcurrentHashMap
꽤 똑똑한 클래스이지만 궁극적으로 여러 스레드가 어떤 식 으로든 문제를 일으키지 않도록 방지해야하며 다른 스레드가 정기적으로 충돌하면 속도가 저하됩니다.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
에서 가져온 코드 스 니펫 .
WeakHashMap<String,T>
는 몇 가지 문제를 소개하고 스레드로부터 안전하지 않습니다. "== 연산자를 사용하여 객체 ID를 테스트하는 메소드가 동일한 키 객체와 함께 사용하기위한 것입니다"-실제로 Thread
객체를 키로 사용하는 것이 더 좋습니다. 사용 사례에 대해 위에서 설명한 Guava weakKeys 맵을 사용하는 것이 좋습니다.
Thread.exit()
호출되고 threadLocals = null;
바로 볼 수 있음을 알 수 있습니다. 이 버그 를 언급하는 주석은 여러분이 읽을 수도 있습니다.
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
스레드에 대해 여러 변수를 원하면 어떻게 합니까? 가장 간단한 방법은 만드는 것입니다 의를threadLocals
HashMap
key
각 항목의는의 이름입니다 ThreadLocal
변수, 그리고 value
각 항목의는의 값이 ThreadLocal
변수입니다. 좀 헷갈 리나요? 두 개의 스레드 t1
와 t2
. 생성자 Runnable
의 매개 변수 와 동일한 인스턴스를 취하고 Thread
둘 다 및 ThreadLocal
라는 두 개의 변수를 갖습니다 . 이게 어떤지.tlA
tlb
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 class
은 WeakReference
. 현재 스레드가 종료되면 자동으로 가비지 수집됩니다. 이것이 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();
}
이것은 전체 그림에서와 같습니다.