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


81

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

回答:


120

ここでの答えはすべて正しいですが、ThreadLocal実装がいかに巧妙であるかをいくらか理解しているため、少しがっかりします。私はちょうどソースコードをThreadLocal見ていて、それがどのように実装されているかに感動しました。

ナイーブな実装

ThreadLocal<T>javadocで説明されているAPIを指定してクラスを実装するように依頼した場合、どうしますか?最初の実装では、キーとしてConcurrentHashMap<Thread,T>を使用する可能性がありThread.currentThread()ます。これはかなりうまく機能しますが、いくつかの欠点があります。

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

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から取得したコードスニペット。


1
あなたの答えは素晴らしく見えますが、今のところ私には長すぎます。+1して承認しました。後で読むために、getpocket.comアカウントに追加します。ありがとう!
ripper234 2013年

map.values()のように、値の完全なリストにアクセスできるThreadLocalのようなものが必要です。したがって、私の素朴な実装はWeakHashMap <String、Object>であり、キーはThread.currentThread()。getName()です。これにより、スレッド自体への参照が回避されます。スレッドがなくなると、スレッドの名前を保持しているものはなくなり(仮定、私は認めます)、私の値はなくなります。
bmauter 2013年

私は実際にその趣旨の質問にごく最近答えました。AWeakHashMap<String,T>はいくつかの問題を引き起こしますが、スレッドセーフではありません。「主に、equalsメソッドが==演算子を使用してオブジェクトの同一性をテストするキーオブジェクトで使用するThreadことを目的としています」-したがって、実際にオブジェクトをキーとして使用する方が適切です。ユースケースとして、上記で説明したGuavaweakKeysマップを使用することをお勧めします。
dimo414 2013年

1
弱いキーは必要ありませんが、同期されたHashMap上でConcurrentHashMapを使用することを検討してください。前者はマルチスレッドアクセス用に設計されており、各スレッドが一般に異なるキーにアクセスしている場合にはるかにうまく機能します。
dimo414 2013年

1
@shmoselこのクラスは高度に調整されているので、そのような考慮がなされているという仮定から始めます。一目見ただけで、スレッドが正常に終了するThread.exit()と呼び出され、threadLocals = null;そこに表示されます。コメントはこのバグを参照しており、これも読むのが楽しいかもしれません。
dimo414 2016年

33

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


定義上、データは単一のスレッドにしか表示されないため、なぜ必要なのかわかりません。
skaffman 2009

8
正解です。スレッド内でのみアクセスされるため、ThreadLocalMapの周囲または内部で同期やロックは行われません。
コーワン

8

JavaのThreadLocal変数は、Thread.currentThread()インスタンスによって保持されているHashMapにアクセスすることによって機能します。


それは正しくありません(または少なくともそれはもはや正しくありません)。Thread.currentThread()は、Thread.classのネイティブ呼び出しです。また、Threadには、ハッシュコードの単一バケット(配列)である「ThreadLocalMap」があります。このオブジェクトは、マップインターフェイスをサポートしていません。
user924272 2014年

1
それは基本的に私が言ったことです。currentThread()は、ThreadLocalsの値へのマップを保持するThreadインスタンスを返します。
クリスベスト

4

実装しようとしているとしたら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。それらRunnableThreadコンストラクターのパラメーターと同じインスタンスを取り、両方ともと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();
}

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

全体像


-1

概念的には、スレッド固有の値を格納するThreadLocal<T>を保持してMap<Thread,T>いると考えることができますが、これは実際に実装される方法ではありません。

スレッド固有の値は、Threadオブジェクト自体に格納されます。スレッドが終了すると、スレッド固有の値をガベージコレクションできます。

参照:JCIP


1
概念的には、そうです。しかし、上記の他の回答からわかるように、実装はまったく異なります。
Archit 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.