1. ホーム
  2. java

[解決済み] Java の ThreadLocal はどのように実装されているのでしょうか?

2023-07-03 18:33:59

質問

ThreadLocalはどのように実装されていますか?Java で実装されているのか (ThreadID からオブジェクトへの並列マップを使用)、それともより効率的に行うために JVM フックを使用しているのでしょうか?

どのように解決するのですか?

ここにある回答はすべて正しいのですが、少し残念なのは、いかに賢いか、ということを多少ごまかしながら ThreadLocal の実装がいかに巧妙であるかを隠してしまうからです。 私はちょうど のソースコードを見ていたら ThreadLocal で、その実装方法に快く感心しました。

ナイーブな実装

もし、私があなたに ThreadLocal<T> クラスを実装するように言われたら、あなたはどうしますか? 最初の実装はおそらく ConcurrentHashMap<Thread,T> を使用して Thread.currentThread() をキーとして使用します。 これはそれなりにうまくいくでしょうが、いくつかの欠点があります。

  • スレッドの競合 ConcurrentHashMap は非常にスマートなクラスですが、最終的にはまだ、複数のスレッドが何らかの方法でそれを操作するのを防がなければならず、異なるスレッドが定期的にそれをヒットする場合、スローダウンが発生します。
  • スレッドが終了して GC した後でも、スレッドとオブジェクトの両方へのポインタを永続的に保持します。

GCフレンドリーな実装

もう一度、ガベージコレクションの問題に対処するために 弱参照 . WeakReferences の扱いはわかりにくいかもしれませんが、このように作られたマップを使えば十分でしょう。

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())

あるいは グアバ (を使っている場合(そうすべきです!)。

new MapMaker().weakKeys().makeMap()

これは、誰もスレッドを保持しなくなったら(つまり終了したら)、キーと値をガベージコレクションできることを意味します。これは改善されましたが、まだスレッドの競合の問題には対処できていません。 ThreadLocal はそれほど素晴らしいクラスではないということです。 さらに、もし誰かが Thread オブジェクトを保持することを決めた場合、それらは決して GC'ed されず、したがって私たちのオブジェクトも、今は技術的に到達不可能であるにもかかわらず、GC'ed されないでしょう。

巧妙な実装

私たちが考えてきたのは ThreadLocal をスレッドから値へのマッピングとして考えてきましたが、実はそれは正しい考え方ではないかもしれません。 各 ThreadLocal オブジェクトのスレッドから値へのマッピングとして考えるのではなく、ThreadLocal オブジェクトから値へのマッピングとして考えたらどうでしょうか。 を各スレッド ? 各スレッドがマッピングを保存し、ThreadLocal が単にそのマッピングへの素晴らしいインターフェイスを提供するなら、以前の実装のすべての問題を回避することができます。

実装は次のようなものになります。

// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()

このマップにアクセスするスレッドは1つだけなので、並行処理について心配する必要はありません。

彼らはThreadクラスを直接開発し、それにフィールドや操作を追加することができますし、それはまさに彼らが行ったことです。

java.lang.Thread には、次のような行があります。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

コメントからわかるように、このマッピングは実際に ThreadLocal オブジェクトによって追跡されるすべての値のパッケージ・プライベート・マッピングです。 Thread . の実装は ThreadLocalMapWeakHashMap ではありませんが、弱参照によるキーの保持を含め、同じ基本的な契約に従っています。

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の値にしかアクセスできません)、したがって、同時実行の問題はありません。 さらに、いったん Thread が終了すると、そのマップは自動的にGCされ、すべてのローカルオブジェクトはクリーンアップされます。 たとえ Thread が保持されている場合、その ThreadLocal オブジェクトは弱い参照で保持され、そのオブジェクトを削除すると同時に ThreadLocal オブジェクトがスコープ外に出た時点でクリーンアップされます。


言うまでもなく、私はこの実装にかなり感銘を受けました。これは非常にエレガントに多くの並行処理の問題を回避し (確かにコア Java の一部であることを利用していますが、これは非常に賢いクラスなので許せます)、一度にひとつのスレッドがアクセスする必要があるオブジェクトへの高速かつスレッドセーフなアクセスを可能にします。

tl;dr ThreadLocal の実装はかなりクールで、一見して思ったよりずっと速い/賢いです。

この回答が気に入ったのであれば、私の (あまり詳細ではない) の議論も評価できるでしょう。 ThreadLocalRandom .

Thread / ThreadLocal から引用したコードスニペット Oracle/OpenJDKのJava 8の実装 .