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
스레드에 대해 여러 변수를 원하면 어떻게 합니까? 가장 간단한 방법은 만드는 것입니다 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);
}
...
}
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:
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
'Programing' 카테고리의 다른 글
Scala에서 암시 적 매개 변수의 좋은 예? (0) | 2020.10.24 |
---|---|
인라인 함수의 정적 변수 (0) | 2020.10.24 |
AMQP / ZeroMQ / RabbitMQ를 사용하는 이유 (0) | 2020.10.24 |
scala.collection.mutable.Map에 요소를 추가하는 구문은 무엇입니까? (0) | 2020.10.24 |
Winform 앱의 기본 버튼 속성 (0) | 2020.10.24 |