ThreadLocal はどのように実装されていますか? それはJavaで実装されていますか(ThreadIDからオブジェクトへの同時マップを使用していますか)、それともJVMフックを使用してより効率的に実行していますか?
5 に答える
ThreadLocal
ここでの答えはすべて正しいのですが、の実装がいかに巧妙であるかについて多少説明しているため、少しがっかりしています。のソースコードをThreadLocal
見ただけで、その実装方法に感銘を受けました。
素朴な実装
javadoc に記述されている API を指定してクラスを実装するように依頼したらThreadLocal<T>
、どうしますか? ConcurrentHashMap<Thread,T>
最初の実装では、キーとしてusingが使用される可能性がありThread.currentThread()
ます。これはかなりうまく機能しますが、いくつかの欠点があります。
- スレッドの競合 -
ConcurrentHashMap
かなりスマートなクラスですが、最終的には複数のスレッドがなんらかの方法で干渉するのを防ぐ必要があり、異なるスレッドが定期的にヒットすると速度が低下します。 - スレッドが終了して GC された後でも、スレッドとオブジェクトの両方へのポインターを永続的に保持します。
GC フレンドリーな実装
もう一度やり直して、弱い参照を使用してガベージ コレクションの問題に対処しましょう。WeakReferences の処理は混乱を招く可能性がありますが、次のように作成されたマップを使用するだけで十分です。
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
または、Guavaを使用している場合(そうすべきです!):
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>()
このマップにアクセスするスレッドは 1 つだけなので、ここでは並行性について心配する必要はありません。
Java 開発者は、ここで私たちよりも大きなアドバンテージを持っています。彼らは直接 Thread クラスを開発し、それにフィールドと操作を追加することができます。それはまさに彼らが行ったことです。
次java.lang.Thread
の行があります。
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal
コメントが示唆しているように、これは実際には、 this のオブジェクトによって追跡されるすべての値のパッケージプライベートマッピングです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 の一部であることを利用することによって確かに、それは非常に賢いクラスなので許されます) 多くの並行性の問題を非常にエレガントに回避し、高速で高速な処理を可能にします。一度に 1 つのスレッドのみがアクセスする必要があるオブジェクトへのスレッドセーフなアクセス。
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
は 1 つしかありません。スレッドにThreadLocal
複数の変数が必要な場合はどうしますか? ThreadLocal
最も簡単な方法は を作成することthreadLocals
ですHashMap
。各エントリの は変数key
の名前であり、各エントリの は変数の値です。ちょっと混乱?2 つのスレッドがあるとしましょう。それらはコンストラクターのパラメーターと同じインスタンスを取り、両方とも とという名前の 2 つの変数を持ちます。こんな感じです。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
は に初期化されているため、最初に次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();
}
全体像としてはこんな感じです。
概念的には、 はスレッド固有の値を格納するThreadLocal<T>
を保持していると考えることができますが、これは実際の実装方法ではありません。Map<Thread,T>
スレッド固有の値は Thread オブジェクト自体に格納されます。スレッドが終了すると、スレッド固有の値をガベージ コレクションできます。
参考:JCIP