ThreadLocalはどのように実装されていますか? Java(ThreadIDからオブジェクトへの同時マップを使用して)に実装されていますか?それとも、JVMフックを使用してより効率的にしていますか?
ここでの答えはすべて正しいですが、ThreadLocal
の実装がいかに巧妙であるかについていくらか光沢があるため、少しがっかりします。私は ThreadLocal
のソースコード を見ているだけで、それがどのように実装されているかにうれしく思いました。
単純な実装
Javadocに記述されているAPIを前提として_ThreadLocal<T>
_クラスを実装するように依頼した場合、どうしますか?最初の実装は、Thread.currentThread()
をキーとして使用する_ConcurrentHashMap<Thread,T>
_になる可能性があります。これは適切に機能しますが、いくつかの欠点があります。
ConcurrentHashMap
はかなりスマートなクラスですが、最終的には、複数のスレッドが何らかの方法でそれに干渉することを防ぐ必要があり、異なるスレッドが定期的にヒットする場合、速度が低下します。GCに適した実装
もう一度やり直してください。ガベージコレクションの問題に対処するには、 weak references を使用します。 WeakReferencesの処理は混乱を招く可能性がありますが、そのように構築されたマップを使用することで十分です。
_ Collections.synchronizedMap(new WeakHashMap<Thread, T>())
_
または Guava を使用している場合(そして使用する必要があります!):
_new MapMaker().weakKeys().makeMap()
_
これは、他の誰もスレッドを保持していない(つまり、それが完了したことを意味する)と、キー/値をガベージコレクションできることを意味します。これは改善ですが、スレッドの競合の問題には対処しません。つまり、これまでのところThreadLocal
がすべてではありませんクラスのすごい。さらに、誰かが終了後にThread
オブジェクトを保持することを決定した場合、それらがGCされることは決してないため、現在技術的に到達不能であっても、オブジェクトはGCされません。
賢い実装
私たちは、ThreadLocal
をスレッドと値のマッピングとして考えてきましたが、それが実際に正しい方法ではないかもしれません。それをThreadsから各ThreadLocalオブジェクトの値へのマッピングとして考えるのではなく、それをThreadLocalオブジェクトの値へのマッピングとして考えたらどうでしょう各スレッド?各スレッドがマッピングを格納し、ThreadLocalがそのマッピングへのNiceインターフェースを提供するだけの場合、以前の実装のすべての問題を回避できます。
実装は次のようになります。
_// 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
の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; drThreadLocal
の実装は非常に優れており、一見したところよりもはるかに高速でスマートです。
この回答が気に入った場合は、私の(あまり詳細ではありません) ThreadLocalRandom
のディスカッション も参考になります。
Thread
/ThreadLocal
コードスニペット Oracle/OpenJDKの実装Java 8 。
もしかして 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
は1つだけになります。スレッドに複数のThreadLocal
変数が必要な場合はどうなりますか?最も簡単な方法は、threadLocals
をHashMap
にすることです。各エントリのkey
はThreadLocal
変数の名前であり、各エントリのvalue
はThreadLocal
変数の値です。少し混乱していますか? _t1
_と_t2
_の2つのスレッドがあるとします。これらは、Runnable
コンストラクターのパラメーターと同じThread
インスタンスを受け取り、両方にThreadLocal
およびtlA
という名前の2つのtlb
変数があります。こんな感じです。
t1.tlA
_+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 0 |
| tlB | 1 |
+-----+-------+
_
t2.tlB
_+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 2 |
| tlB | 3 |
+-----+-------+
_
値は私が作り上げていることに注意してください。
今では完璧に思えます。しかし、_ThreadLocal.ThreadLocalMap
_とは何ですか?なぜHashMap
を使用しなかったのですか?問題を解決するために、ThreadLocal
クラスのset(T value)
メソッドを使用して値を設定するとどうなるかを見てみましょう。
_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
クラスのコア部分は、WeakReference
を拡張する_Entry class
_です。これは、現在のスレッドが存在する場合、自動的にガベージコレクションされることを保証します。これが、単純な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();
}
_
これは全体像のようなものです: