ホームページ  >  記事  >  Java  >  Java ThreadLocalクラスの使い方

Java ThreadLocalクラスの使い方

王林
王林転載
2023-05-14 18:49:061092ブラウズ

    #図に示すように:

    Java ThreadLocalクラスの使い方#クイック スタート

    次に、簡単な例を使用して、ThreadLocal の基本的な使用法を示します。

    package cuit.pymjl.thradlocal;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/1 10:56
     **/
    public class MainTest {
        static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        static void print(String str) {
            //打印当前线程中本地内存中本地变量的值
            System.out.println(str + " :" + threadLocal.get());
            //清除本地内存中的本地变量
            threadLocal.remove();
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    threadLocal.set("thread1 local variable");
                    //调用打印方法
                    print("thread1");
                    //打印本地变量
                    System.out.println("after remove : " + threadLocal.get());
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    threadLocal.set("thread2 local variable");
                    //调用打印方法
                    print("thread2");
                    //打印本地变量
                    System.out.println("after remove : " + threadLocal.get());
                }
            });
    
            t1.start();
            t2.start();
        }
    }

    実行結果は、図に示すとおりです。

    Java ThreadLocalクラスの使い方ThreadLocal の原理

    ThreadLocal 関連クラス図

    まず、次に示すように、ThreadLocal 関連クラスのクラス図構造を見てみましょう。

    Java ThreadLocalクラスの使い方 この図からわかるように、Thread クラスには threadLocals と継承可能なThreadLocals があり、どちらも ThreadLocalMap 型の変数です。 、ThreadLocalMap はカスタマイズされたハッシュマップです。デフォルトでは、各スレッドのこれら 2 つの変数は null であり、現在のスレッドが初めて ThreadLocal の set メソッドまたは get メソッドを呼び出したときにのみ作成されます。実際、各スレッドのローカル変数は ThreadLocal インスタンスではなく、呼び出しスレッドの threadLocals 変数に格納されます。つまり、ThreadLocal 型のローカル変数は、特定のスレッド メモリ空間に格納されます。 ThreadLocal は、set メソッドを通じて呼び出しスレッドの threadLocals に値を入れて保存するツール シェルです。呼び出しスレッドが get メソッドを呼び出すと、現在のスレッドの threadLocals 変数から値を取り出して使用します。呼び出しスレッドが終了しない場合、このローカル変数は常に呼び出しスレッドの threadLocals 変数に格納されます。したがって、ローカル変数が必要ないときは、次のコマンドを呼び出して、現在のスレッドの threadLocals からローカル変数を削除できます。 ThreadLocal 変数のメソッドを削除します。さらに、Thread の threadLocals がマップ構造として設計されているのはなぜですか?各スレッドを複数の ThreadLocal 変数に関連付けることができるため、これは明らかです。次に、ThreadLocal の set、get、remove のソース コードを見てみましょう。

    set

        public void set(T value) {
            // 1.获取当前线程(调用者线程)
            Thread t = Thread.currentThread();
            // 2.以当前线程作为key值,去查找对应的线程变量,找到对应的map
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 3.如果map不为null,则直接添加元素
                map.set(this, value);
            } else {
                // 4.否则就先创建map,再添加元素
                createMap(t, value);
            }
        }
        void createMap(Thread t, T firstValue) {
            /**
             * 这里是创建一个ThreadLocalMap,以当前调用线程的实例对象为key,初始值为value
             * 然后放入当前线程的Therad.threadLocals属性里面
             */
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        ThreadLocalMap getMap(Thread t) {
            //这里就是直接获取调用线程的成员属性threadlocals
            return t.threadLocals;
        }

    get

        public T get() {
            // 1.获取当前线程
            Thread t = Thread.currentThread();
            // 2.获取当前线程的threadlocals,即ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            // 3.如果map不为null,则直接返回对应的值
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            // 4.否则,则进行初始化
            return setInitialValue();
        }

    次は、

    setInitialValue のコードです。

    ##

    private T setInitialValue() {
        //初始化属性,其实就是null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //通过当前线程获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //如果map不为null,则直接添加元素
        if (map != null) {
            map.set(this, value);
        } else {
            //否则就创建,然后将创建好的map放入当前线程的属性threadlocals
            createMap(t, value);
        }
            //将当前ThreadLocal实例注册进TerminatingThreadLocal类里面
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
    ここでいくつか説明を追加する必要がありますTerinatedThreadLocal

    。このクラスは jdk11 で新しく追加されたもので、jdk8 には存在しないため、インターネット上の多くのソース コード分析ではこのクラスに関連する説明がありません。このクラスのソース コードを確認したところ、その機能は ThreadLocal メモリ リークの問題を回避することになっているはずです (興味があれば、ソース コードを見て、間違いがあれば修正してください) )。これが公式の説明です:

    /**
     * A thread-local variable that is notified when a thread terminates and
     * it has been initialized in the terminating thread (even if it was
     * initialized with a null value).
     * 一个线程局部变量,
     * 当一个线程终止并且它已经在终止线程中被初始化时被通知(即使它被初始化为一个空值)。
     */
    remove
         public void remove() {
             //如果当前线程的threadLocals 变量不为空, 则删除当前线程中指定ThreadLocal 实例的本地变量。
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null) {
                 m.remove(this);
             }
         }

    summary

    各スレッド内には、threadLocals という名前のメンバー変数があります。この変数の型はハッシュ マップです。ここで、キーはは定義した ThreadLocal 変数の this 参照であり、value は set メソッドを使用して設定した値です。各スレッドのローカル変数は、スレッド自身のメモリ変数 threadLocals に格納されます。現在のスレッドが終了しない場合、これらのローカル変数は常に存在するため、メモリ オーバーフローが発生する可能性があります。したがって、削除後に ThreadLocal の Remove メソッドを呼び出すことを忘れないでください。スレッドに対応する threadLocals のローカル変数。

    ThreadLocal メモリ リーク

    メモリ リークはなぜ発生するのでしょうか?

    ThreadLocalMap は、ThreadLocal の弱参照をキーとして使用します。ThreadLocal がそれを参照するための外部強参照を持たない場合、ThreadLocal はシステム GC 中に必然的にリサイクルされます。 、ThreadLocalMap は、null キーを持つエントリが表示された場合、

    、null キーを使用してこれらのエントリの値にアクセスする方法はありません。

    現在のスレッドが長時間終了しない場合、常に存在します。参照チェーン: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value は決してリサイクルできず、メモリ リークが発生します。

    実際、この状況は ThreadLocalMap の設計で考慮されており、いくつかの保護措置が追加されています。スレッド ThreadLocalMap 内の null であるすべてのキーは、get()、set()、および Remove の実行中にクリアされます。 ThreadLocal.値の()。ただし、これらの受動的な予防策では、メモリ リークが発生しないことを保証できません。静的 ThreadLocal を使用すると、ThreadLocal のライフ サイクルが延長され、メモリ リークが発生する可能性があります。

    • 割り当てでは ThreadLocal が使用され、メモリ リークにつながる get()、set()、remove() メソッドは呼び出されなくなりました。

    • 弱参照を使用する理由

    • 弱い参照を使用すると ThreadLocalMap のメモリ リークが発生することは誰もが知っているのに、なぜ当局は依然として強参照ではなく弱い参照を使用するのでしょうか?これは、弱参照と強参照の使用の違いから始まります。
    • 強参照を使用する場合: ThreadLocalMap のライフ サイクルは基本的に Thread のライフ サイクルと同じであることがわかっています。現在のスレッドが終了しない場合、ThreadLocalMap は GC によってリサイクルされません。と ThreadLocalMap が ThreadLocal の権利を保持します。強い参照を行うと、ThreadLocal はリサイクルされません。スレッドのライフサイクルが長い場合、手動で削除しないと kv が蓄積され、OOM

    • # が発生します。
    • ##弱い参照を使用する場合: 弱い 参照内のオブジェクトの宣言期間は短いです。これは、システム GC 中に、弱い参照が見つかった限り、ヒープ領域が残っているかどうかに関係なく、オブジェクトがリサイクルされるためです。十分な。 ThreadLocal の強参照を再利用すると、ThreadLocalMap が保持する弱参照も再利用されるため、手動で kv を削除しないと値が蓄積され、OOM

    比較より、弱参照を使用すると、少なくともマップ キーの蓄積によって OOM が発生しないことが保証され、対応する値は次の呼び出し時に、remove、get、set メソッドを通じてクリアできることがわかります。メモリ リークの根本原因は弱参照ではなく、ThreadLocalMap のライフ サイクルが Thread と同じくらい長いため、蓄積が発生していることがわかります。 OOM を引き起こす値の蓄積です。その後、適切な治療法を講じ、ThreadLocal を使用してクリーンアップするたびに

    remove()

    メソッドを呼び出します。

    以上がJava ThreadLocalクラスの使い方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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