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つの変数を持っています。こんな感じです。tlAtlb
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