ThreadLocalはどのように実装されていますか?Javaで実装されていますか(ThreadIDからオブジェクトへの同時マップを使用)、それともJVMフックを使用してより効率的に実装していますか?
回答:
ここでの答えはすべて正しいですが、ThreadLocal
実装がいかに巧妙であるかをいくらか理解しているため、少しがっかりします。私はちょうどソースコードをThreadLocal
見ていて、それがどのように実装されているかに感動しました。
ナイーブな実装
ThreadLocal<T>
javadocで説明されているAPIを指定してクラスを実装するように依頼した場合、どうしますか?最初の実装では、キーとしてConcurrentHashMap<Thread,T>
を使用する可能性がありThread.currentThread()
ます。これはかなりうまく機能しますが、いくつかの欠点があります。
ConcurrentHashMap
かなり賢いクラスですが、最終的には、複数のスレッドが何らかの方法でそれをいじるのを防ぐことに対処する必要があり、異なるスレッドが定期的にそれに当たると、速度が低下します。GCに適した実装
もう一度やり直してください。弱参照を使用してガベージコレクションの問題に対処しましょう。WeakReferencesの処理は混乱を招く可能性がありますが、次のように作成されたマップを使用するだけで十分です。
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
または、Guavaを使用している場合(そして使用する必要があります!):
new MapMaker().weakKeys().makeMap()
これは、他の誰もスレッドを保持していない場合(終了したことを意味します)、キー/値をガベージコレクションできることを意味します。これは改善ですが、スレッドの競合の問題には対処していません。つまり、これまでのところ、それだけでThreadLocal
はありません。クラスのすごい。さらに、Thread
オブジェクトが終了した後に誰かがオブジェクトを保持することを決定した場合、それらはGCされることはありません。したがって、現在技術的に到達できない場合でも、オブジェクトはGCされません。
巧妙な実装
私たちはThreadLocal
スレッドから値へのマッピングとして考えてきましたが、それは実際にはそれについて考える正しい方法ではないかもしれません。スレッドから各ThreadLocalオブジェクトの値へのマッピングとして考えるのではなく、ThreadLocalオブジェクトから各スレッドの値へのマッピングとして考えた場合はどうでしょうか。各スレッドがマッピングを格納し、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
このオブジェクトによって追跡されているすべての値のパッケージプライベートマッピングです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のJava8の実装ThreadLocal
から取得したコードスニペット。
WeakHashMap<String,T>
はいくつかの問題を引き起こしますが、スレッドセーフではありません。「主に、equalsメソッドが==演算子を使用してオブジェクトの同一性をテストするキーオブジェクトで使用するThread
ことを目的としています」-したがって、実際にオブジェクトをキーとして使用する方が適切です。ユースケースとして、上記で説明したGuavaweakKeysマップを使用することをお勧めします。
Thread.exit()
と呼び出され、threadLocals = null;
そこに表示されます。コメントはこのバグを参照しており、これも読むのが楽しいかもしれません。
つまり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
変数の値です。少し紛らわしいですか?2つのスレッドがあるt1
としましょうt2
。それらRunnable
はThread
コンストラクターのパラメーターと同じインスタンスを取り、両方ともとThreadLocal
という名前の2つの変数を持っています。こんな感じです。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();
}
これは全体像のようです:
概念的には、スレッド固有の値を格納するThreadLocal<T>
を保持してMap<Thread,T>
いると考えることができますが、これは実際に実装される方法ではありません。
スレッド固有の値は、Threadオブジェクト自体に格納されます。スレッドが終了すると、スレッド固有の値をガベージコレクションできます。
参照:JCIP