web-dev-qa-db-ja.com

JavaのThreadLocalは内部でどのように実装されていますか?

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

73
ripper234

ここでの答えはすべて正しいですが、ThreadLocalの実装がいかに巧妙であるかについていくらか光沢があるため、少しがっかりします。私は ThreadLocalのソースコード を見ているだけで、それがどのように実装されているかにうれしく思いました。

単純な実装

Javadocに記述されているAPIを前提として_ThreadLocal<T>_クラスを実装するように依頼した場合、どうしますか?最初の実装は、Thread.currentThread()をキーとして使用する_ConcurrentHashMap<Thread,T>_になる可能性があります。これは適切に機能しますが、いくつかの欠点があります。

  • スレッドの競合-ConcurrentHashMapはかなりスマートなクラスですが、最終的には、複数のスレッドが何らかの方法でそれに干渉することを防ぐ必要があり、異なるスレッドが定期的にヒットする場合、速度が低下します。
  • スレッドが終了し、GCされた後でも、スレッドとオブジェクトの両方へのポインタを永続的に保持します。

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;
_

コメントが示唆するように、これは実際に、このThreadLocalThreadオブジェクトによって追跡されているすべての値のパッケージプライベートマッピングです。 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

105
dimo414

もしかして Java.lang.ThreadLocal。これは非常に単純で、実際には、各Threadオブジェクト内に保存されている名前と値のペアのマップにすぎません(Thread.threadLocalsフィールド)。 APIはその実装の詳細を隠しますが、それは多かれ少なかれすべてです。

32
skaffman

Java=のThreadLocal変数は、Thread.currentThread()インスタンスが保持するHashMapにアクセスすることで機能します。

8
Chris Vest

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しかないため、threadLocalsThreadLocalとして単純に取ると(たとえば、threadLocalsをIntegerとして定義すると)、特定のスレッドに対してThreadLocalは1つだけになります。スレッドに複数のThreadLocal変数が必要な場合はどうなりますか?最も簡単な方法は、threadLocalsHashMapにすることです。各エントリのkeyThreadLocal変数の名前であり、各エントリのvalueThreadLocal変数の値です。少し混乱していますか? _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();
}
_

これは全体像のようなものです:

The Whole Picture

2
Searene