ホームページ  >  記事  >  Java  >  ThreadLocalの実装原理の分析と紹介(コード付き)

ThreadLocalの実装原理の分析と紹介(コード付き)

不言
不言転載
2019-02-16 13:37:472982ブラウズ

この記事の内容は、ThreadLocal の実装原理の分析と紹介です (コード付き)。必要な方は参考にしていただければ幸いです。

ThreadLocal はスレッド ローカル変数であり、それを使用するスレッドごとに変数の独立したコピーを維持するために使用されます。この変数は、スレッドのライフサイクル中のみ有効です。また、時間をスペースと引き換えにするロック メカニズムとは異なり、ThreadLocal には、変数のスレッドの安全性を確保するためにスペースを時間を引き換えにするロック メカニズムがありません。

この記事では、ThreadLocal の実装原理をソース コードから分析します。

まず ThreadLocal クラス図の構造を見てみましょう

SuppliedThreadLocal は主に JDK1.8 で Lambda 式のサポートを拡張するために使用されます。興味のある方は Baidu を参照してください。

ThreadLocalMap は ThreadLocal の静的な内部クラスであり、実際に変数を保存するクラスです。

Entry は、ThreadLocalMap の静的な内部クラスです。 ThreadLocalMap は、ThreadLocal をキー、変数を値として持つ Entry 配列を保持し、Entry をカプセル化します。

次の図は、Thread、ThreadLocal、ThreadLocalMap、および Entry の関係を簡単に説明したものです。


上の図について説明してください:

  1. スレッドは ThreadLocalMap オブジェクトを所有します

  2. ThreadLocalMap は Entry 配列を所有します

  3. 各エントリには k--v があります

  4. エントリのキーは特定の ThreadLocal オブジェクトです

主なメソッドを以下で分析します。

1.set()

##

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

これはここでわかります: Thread には ThreadLocalMap オブジェクトが 1 つだけあります。特定の格納値は ThreadLocalMap の set() と呼ばれ、渡されるパラメータ キーは現在の ThreadLocal オブジェクトです。

ThreadLocalMap の set() メソッドをもう一度見てください:

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); // 1

            for (Entry e = tab[i];  // 2
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value); // 3
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
                rehash();
        }

  1. #キーのハッシュコードと配列容量の剰余を取得して配列インデックスを計算します -1

  2. 現在のインデックスからトラバースし、無効をクリアしますnull キーを持つエントリ

  3. ##K-V をエントリとしてカプセル化し、配列に配置します

  4. エントリの配列拡張が必要かどうかを判断します。しきい値の値はアレイ容量の 2/3 です。

  5. # 展開のsize()メソッドを見てみましょう:

#

private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

ここで重要なのは、容量を元のサイズの 2 倍に拡張することです。次に、古い配列を反復処理し、新しい配列の容量に基づいて新しい配列内のエントリの位置を再計算します。

2. get()

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();
    }

ThreadLocalMap の getEntry() メソッドは次のとおりです:

##
private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1); // 1
            Entry e = table[i];
            if (e != null && e.get() == key) // 2
                return e;
            else
                return getEntryAfterMiss(key, i, e); //3
        }

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) { //4
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }


インデックスの計算

  1. 現在のインデックスのエントリが空ではなく、キーが同じです。直接戻ります。

  2. それ以外の場合は、隣接するインデックスに移動して

  3. を検索します。無効なキーが見つかった場合はクリアされます。見つかったらサイクルを終了します。

  4. 3. 削除()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
処理方法は検索と保存と同様で、該当するエントリを削除した後、null キーを持つ無効な要素が削除されます。

#注

static class Entry extends WeakReference<ThreadLocal<?>> {}
ThreadLocal には OOM の問題がある可能性があります。 ThreadLocalMap は ThreadLocal の弱参照をキーとして使用するため、GC が発生するとキーが再利用されるため、値自体がより大きなオブジェクトの場合、スレッドが終了しない場合は、null キーで value 要素にアクセスできません。値は、これまでリサイクルできなかったものになります。特にスレッド プールを使用する場合、スレッドは再利用され、スレッドは強制終了されません。このように、ThreadLocal の弱参照がリサイクルされるときに、値はリサイクルされません。

ThreadLocal を使用する場合、スレッド ロジック コードの終了時に ThreadLocal.remove() メソッドを明示的に呼び出す必要があります。

以上がThreadLocalの実装原理の分析と紹介(コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。