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 つの変数を持ちます。こんな感じです。ThreadLocalvalueThreadLocalt1t2RunnableThreadThreadLocaltlAtlb
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
