ホームページ  >  記事  >  Java  >  JavaでThreadLocalクラスを使用するにはどうすればよいですか?

JavaでThreadLocalクラスを使用するにはどうすればよいですか?

王林
王林転載
2023-05-08 23:49:061179ブラウズ

    Threadlocal の用途:

    簡単に言うと、ThreadLocal はスレッド内で共有され、異なるスレッド間で分離されます (各スレッドは独自のスレッドの値のみが表示されます)。以下に示すように:

    JavaでThreadLocalクラスを使用するにはどうすればよいですか?

    #ThreadLocal の使用例

    API の紹介

    Threadlocal を使用する前に、次の API を見てみましょう:

    JavaでThreadLocalクラスを使用するにはどうすればよいですか?

    ThreadLocal クラスの API は非常に単純です。ここでより重要なものは get()、set()、remove() です。set は代入操作に使用されます。 get は変数を取得するために使用されます。Value、remove は現在の変数の値を削除するために使用されます。initialValue メソッドは初めて呼び出されたときにトリガーされ、現在の変数の値を初期化するために使用されることに注意してください。デフォルトでは、initialValue は null を返します。

    ThreadLocal の使用

    ThreadLocal クラスの API についての説明は終わりましたので、前の文を理解するために練習しましょう: ThreadLocal はスレッド内で共有されます。異なるスレッド (各スレッドは自身のスレッドの値のみを参照できます)

    public class ThreadLocalTest {
    
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    	// 重写这个方法,可以修改“线程变量”的初始值,默认是null
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
    
        public static void main(String[] args) throws InterruptedException {
    
            //一号线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("一号线程set前:" + threadLocal.get());
                    threadLocal.set(1);
                    System.out.println("一号线程set后:" + threadLocal.get());
                }
            }).start();
    
            //二号线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("二号线程set前:" + threadLocal.get());
                    threadLocal.set(2);
                    System.out.println("二号线程set后:" + threadLocal.get());
    
                }
            }).start();
    
            //主线程睡1s
            Thread.sleep(1000);
    
            //主线程
            System.out.println("主线程的threadlocal值:" + threadLocal.get());
    
        }
    
    }

    上記のコードを少し説明します:

    各 ThreadLocal インスタンスは A 変数名に似ていますが、異なる ThreadLocal インスタンスは異なります。 (とりあえずこのように理解します) 以下の説明で言う「ThreadLocal変数またはスレッド変数」はThreadLocalクラスのインスタンスを表します。

    クラス内に静的な「ThreadLocal 変数」を作成し、メインスレッドに 2 つのスレッドを作成し、これら 2 つのスレッドでそれぞれ ThreadLocal 変数を 1 と 2 に設定します。その後、スレッド No.1 とスレッド No.2 の実行が終了するのを待ち、メインスレッドの ThreadLocal 変数の値を確認します。

    プログラム結果と分析⌛

    JavaでThreadLocalクラスを使用するにはどうすればよいですか?

    プログラム結果の重要な点は、メインスレッドの出力が 0 であることです。通常の変数の場合、番号 1 のスレッド、2 番目のスレッドで通常の変数を 1 と 2 に設定し、最初と 2 番目のスレッドが実行された後にこの変数を出力します。出力値は 1 または 2 でなければなりません (どちらが出力されるかはスレッドによって異なります)オペレーティング システムのスケジュール ロジック)。ただし、ThreadLocal 変数を使用して 2 つのスレッドを通じて値を割り当てた後、メインスレッドのスレッドでは初期値 0 が出力されます。これが、「ThreadLocal がスレッド内で共有され、異なるスレッド間で分離される」理由です。各スレッドは、自身のスレッドの値のみを参照できます。これが ThreadLocal の中心的な役割です。スレッドのスコープ付きローカル変数を実装することです。

    Threadlocal のソース コード分析

    Principle

    各 Thread オブジェクトには ThreadLocalMap があります。ThreadLocal が作成されると、ThreadLocal オブジェクトは Map に追加されます。キーは ThreadLocal で、値は任意の型にすることができます。この文章を最初に読んだときはよく理解できないかもしれませんが、ソースコードを一緒に読んでいくと理解できるようになります。

    これまでの理解では、すべての定数値または参照型は ThreadLocal インスタンスに格納されると考えられていましたが、実際にはそうではありません。このステートメントにより、ThreadLocal 変数の概念がよりよく理解できるようになります。 ThreadLocal に値を格納すると、実際には現在のスレッド オブジェクトの ThreadLocalMap に値が格納されます。ThreadLocalMap は単純に Map として理解でき、この Map に値を格納するためのキーは ThreadLocal インスタンス自体です。

    ソース コード

    JavaでThreadLocalクラスを使用するにはどうすればよいですか?

    ??????つまり、保存したい ThreadLocal 内のデータは、実際には ThreadLocal オブジェクトに保存されません。 , この ThreadLocal インスタンスはキーとして使用され、現在のスレッドの Map に格納されます。ThreadLocal の値を取得する場合も同様です。これが、ThreadLocal がスレッド間の分離を実現できる理由です。

    内部クラス ThreadLocalMap

    ThreadLocalMap は ThreadLocal の内部クラスであり、独自の Map 構造のセットを実装します✨

    ThreadLocalMap 属性:

    static class Entry extends WeakReference<ThreadLocal<?>> {
                Object value;
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            //初始容量16
            private static final int INITIAL_CAPACITY = 16;
            //散列表
            private Entry[] table;
            //entry 有效数量 
            private int size = 0;
            //负载因子
            private int threshold;

    ThreadLocalMap ThreadLocal 変数を設定します

    private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                
                //与运算  & (len-1) 这就是为什么 要求数组len 要求2的n次幂 
                //因为len减一后最后一个bit是1 与运算计算出来的数值下标 能保证全覆盖 
                //否者数组有效位会减半 
                //如果是hashmap 计算完下标后 会增加链表 或红黑树的查找计算量 
                int i = key.threadLocalHashCode & (len-1);
                
                // 从下标位置开始向后循环搜索  不会死循环  有扩容因子 必定有空余槽点
                for (Entry e = tab[i];   e != null;  e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
                    //一种情况 是当前引用 返回值
                    if (k == key) {
                        e.value = value;
                        return;
                    }
                    //槽点被GC掉 重设状态 
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    			//槽点为空 设置value
                tab[i] = new Entry(key, value);
                //设置ThreadLocal数量
                int sz = ++size;
    			
    			//没有可清理的槽点 并且数量大于负载因子 rehash
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

    ThreadLocalMap 属性の概要???:

    • は通常のハッシュマップと同様の配列に格納されますが、これは、で使用されるジッパー メソッドとは異なります。ハッシュの競合を解決するためのハッシュマップ。ThreadLocalMap はオープン アドレス メソッドを使用します。

    • 配列の初期容量は 16、負荷係数は 2/3

    • ノードノードのキーは、リサイクルのためのWeakReferenceをカプセル化します

    • ThreadLocalMapの格納場所
    ##Threadに格納され、2つのThreadLocalMap変数があります

    #ThreadLocal の ThreadLocals オブジェクト メソッド セットでの作成も 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);
        }
    
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    JavaでThreadLocalクラスを使用するにはどうすればよいですか?inheritableThreadLocals は ThreadLocal に似ており、InheritableThreadLocal は createMap メソッドをオーバーライドします

    void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }

    inheritableThreadLocals 作用是将ThreadLocalMap传递给子线程

    JavaでThreadLocalクラスを使用するにはどうすればよいですか?

    init方法中 条件满足后直接为子线程创建ThreadLocalMap

    JavaでThreadLocalクラスを使用するにはどうすればよいですか?

    注意:

    • 仅在初始化子线程的时候会传递 中途改变副线程的inheritableThreadLocals 变量 不会将影响结果传递到子线程 。

    • 使用线程池要注意 线程不回收 尽量避免使用父线程的inheritableThreadLocals 导致错误

    Key的弱引用问题

    为什么要用弱引用,官方是这样回答的

    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

    为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。

    生命周期长的线程可以理解为:线程池的核心线程

    ThreadLocal在没有外部对象强引用时如Thread,发生GC时弱引用Key会被回收,而Value是强引用不会回收,如果创建ThreadLocal的线程一直持续运行如线程池中的线程,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

    • key 使用强引用????: 引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

    • key 使用弱引用????: 引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

    Java8中已经做了一些优化如,在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。

    java中的四种引用

    • 强引用????: 如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象

    • 软引用????: 在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。(软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性)

    • 弱引用????: 具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象

    • 虚引用????: 虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。(注意哦,其它引用是被JVM回收后才被传入ReferenceQueue中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。可以使用在对象销毁前的一些操作,比如说资源释放等。)

    通常ThreadLocalMap的生命周期跟Thread(注意线程池中的Thread)一样长,如果没有手动删除对应key(线程使用结束归还给线程池了,其中的KV不再被使用但又不会GC回收,可认为是内存泄漏),一定会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,Java8已经做了上面的代码优化。

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

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