ホームページ  >  記事  >  Java  >  JavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析

JavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析

WBOY
WBOY転載
2023-04-21 15:19:081505ブラウズ

    はじめに

    ThreadLocal は、マルチスレッド環境で各スレッドが独自の一意のデータを持ち、上から下へデータを渡すことができるようにする方法を提供します。スレッドの実行全体にわたって。

    1. 使い方のデモ

    ThreadLocal を使ったことのない学生も多いと思いますので、まずは ThreadLocal の使い方をデモしてみましょう。デモは次のとおりです:

    /**
     * ThreadLocal 中保存的数据是 Map
     */
    static final ThreadLocal<Map<String, String>> context = new ThreadLocal<>();
    @Test
    public void testThread() {
      // 从上下文中拿出 Map
      Map<String, String> contextMap = context.get();
      if (CollectionUtils.isEmpty(contextMap)) {
        contextMap = Maps.newHashMap();
      }
      contextMap.put("key1", "value1");
      context.set(contextMap);
      log.info("key1,value1被放到上下文中");
    	// 从上下文中拿出刚才放进去的数据
      getFromComtext();
    }
    private String getFromComtext() {
      String value1 = context.get().get("key1");
      log.info("从 ThreadLocal 中取出上下文,key1 对应的值为:{}", value1);
      return value1;
    }
    //运行结果:
    demo.ninth.ThreadLocalDemo - key1,value1被放到上下文中
    demo.ninth.ThreadLocalDemo - 从 ThreadLocal 中取出上下文,key1 对应的值为:value1

    ご覧のとおり実行結果では、key1 に対応する値がコンテキストから取得されています。

    getFromComtext メソッドは入力パラメーターを受け入れません。このコード行 context.get().get("key1") を通じて、key1 の値がコンテキストから取得されます。次に、 ThreadLocal を見てください。基礎となる層がコンテキスト転送を実装する方法。

    2. クラス構造

    2.1. クラス ジェネリックス

    ThreadLocal はジェネリックスを使用してクラスを定義し、ThreadLocal が任意の形式でデータを保存できることを示します。ソース コードは次のとおりです。

    public class ThreadLocal<T> {}

    2.2. 主要な属性

    ThreadLocal にはいくつかの主要な属性があります。1 つずつ見てみましょう:

    // threadLocalHashCode 表示当前 ThreadLocal 的 hashCode,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 计算 ThreadLocal 的 hashCode 值(就是递增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
    private static AtomicInteger nextHashCode = new AtomicInteger();

    もう 1 つの重要な属性もあります: ThreadLocalMap。スレッドに複数の ThreadLocal がある コンテナが実行されている場合、コンテナは複数の ThreadLocal を管理する必要があり、ThreadLocalMap の役割はスレッド内の複数の ThreadLocal を管理することです。

    2.2.1、ThreadLocalMap

    ThreadLocalMap自体は単純なMap構造で、キーはThreadLocal、値はThreadLocalによって保存された値、最下層は配列のデータ構造です。ソース コードは次のとおりです:

    // threadLocalHashCode 表示当前 ThreadLocal 的 hashCode,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 计算 ThreadLocal 的 hashCode 值(就是递增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
    private static AtomicInteger nextHashCode = new AtomicInteger();

    ソース コードから、ThreadLocalMap は実際には単純な Map 構造であることがわかります。最下層は、初期化サイズと拡張しきい値サイズを持つ配列です。要素配列の要素は Entry で、Entry のキーは ThreadLocal への参照、値は ThreadLocal の値です。

    3. ThreadLocal はスレッド間のデータ分離をどのように実現しますか?

    ThreadLocal はスレッドセーフであり、主に ThreadLocalMap がスレッドの属性であるため、安心して使用できます。スレッド Thread で排他的に分離されたソース コード。

    親スレッドが子スレッドを作成すると、inheritableThreadLocals の値はコピーされますが、threadLocals の値はコピーされません。ソース コードは次のとおりです: JavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析

    上から この図では、スレッドが作成されると、親スレッドの継承可能なThreadLocals属性値がコピーされることがわかります。

    4. Set メソッド JavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析

    set メソッドの主な機能は、現在の ThreadLocal に値を設定することです。現在の ThreadLocal のジェネリック タイプが Map の場合は、現在の ThreadLocal にマップします。ソース コードは次のとおりです:

    // set 操作每个线程都是串行的,不会有线程安全的问题
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // 当前 thradLocal 之前有设置值,直接设置,否则初始化
        if (map != null)
            map.set(this, value);
        // 初始化ThreadLocalMap
        else
            createMap(t, value);
    }

    コード ロジックは比較的明確です。次の ThreadLocalMap.set のソース コードを見てみましょう:

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算 key 在数组中的下标,其实就是 ThreadLocal 的 hashCode 和数组大小-1取余
        int i = key.threadLocalHashCode & (len-1);
     
        // 整体策略:查看 i 索引位置有没有值,有值的话,索引位置 + 1,直到找到没有值的位置
        // 这种解决 hash 冲突的策略,也导致了其在 get 时查找策略有所不同,体现在 getEntryAfterMiss 中
        for (Entry e = tab[i];
             e != null;
             // nextIndex 就是让在不超过数组长度的基础上,把数组的索引位置 + 1
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 找到内存地址一样的 ThreadLocal,直接替换
            if (k == key) {
                e.value = value;
                return;
            }
            // 当前 key 是 null,说明 ThreadLocal 被清理了,直接替换掉
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 当前 i 位置是无值的,可以被当前 thradLocal 使用
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // 当数组大小大于等于扩容阈值(数组大小的三分之二)时,进行扩容
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

    上記のソース コードのいくつかの点に注意してください:

    インクリメントする AtomicInteger は ThreadLocal のハッシュコードとして使用されます;

    • 式配列のインデックス位置を計算するためのハッシュコードは、配列サイズを法としたハッシュコードです。ハッシュコードは増加し続けるため、異なるハッシュコードが同じ配列のインデックス位置を計算する可能性が高くなります(実際のプロジェクトではこれを気にしないでください) , ThreadLocal は非常に少なく、基本的に競合はありません);

    • hashCode によって計算インデックス位置 i にすでに値がある場合は、i から開始して検索を続けます空のインデックス位置が見つかるまで 1 から逆方向に進み、現在の ThreadLocal をキーとして置きます。

    • 幸いなことに、日常業務で ThreadLocal を使用する場合、ThreadLocal は 1 ~ 2 つしか使用しないことが多く、ハッシュを介して重複した配列を計算する確率はそれほど高くありません。

      set 中の配列要素の位置の競合を解決する戦略は、get メソッドにも影響します。get メソッドについても一緒に見てみましょう。
    5. Get メソッド

    get メソッドは、主に ThreadLocalMap から現在の ThreadLocal に格納されている値を取得します。ソース コードは次のとおりです。 ThreadLocalMap の getEntry メソッドのソース コードは次のとおりです。

    public T get() {
        // 因为 threadLocal 属于线程的属性,所以需要先把当前线程拿出来
        Thread t = Thread.currentThread();
        // 从线程中拿到 ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 从 map 中拿到 entry,由于 ThreadLocalMap 在 set 时的 hash 冲突的策略不同,导致拿的时候逻辑也不太一样
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果不为空,读取当前 ThreadLocal 中保存的值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 否则给当前线程的 ThreadLocal 初始化,并返回初始值 null
        return setInitialValue();
    }
    // 得到当前 thradLocal 对应的值,值的类型是由 thradLocal 的泛型决定的
    // 由于 thradLocalMap set 时解决数组索引位置冲突的逻辑,导致 thradLocalMap get 时的逻辑也是对应的
    // 首先尝试根据 hashcode 取模数组大小-1 = 索引位置 i 寻找,找不到的话,自旋把 i+1,直到找到索引位置不为空为止
    private Entry getEntry(ThreadLocal<?> key) {
        // 计算索引位置:ThreadLocal 的 hashCode 取模数组大小-1
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回,否则就是没有找到,继续通过 getEntryAfterMiss 方法找
        if (e != null && e.get() == key)
            return e;
        else
        // 这个取数据的逻辑,是因为 set 时数组索引位置冲突造成的  
            return getEntryAfterMiss(key, i, e);
    }

    get ロジック ソース コード内のコメントは非常に明確に記述されているため、繰り返しません。

    6. 拡張

    ThreadLocalMap 内の ThreadLocal の数がしきい値を超えると、ThreadLocalMap は拡張を開始します。拡張ロジックを見てみましょう:

    // 自旋 i+1,直到找到为止
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        // 在大量使用不同 key 的 ThreadLocal 时,其实还蛮耗性能的
        while (e != null) {
            ThreadLocal<?> k = e.get();
            // 内存地址一样,表示找到了
            if (k == key)
                return e;
            // 删除没用的 key
            if (k == null)
                expungeStaleEntry(i);
            // 继续使索引位置 + 1
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

    ソース コードアノテーションも比較的明確で、次の 2 つの点に注目します:

    拡張後の配列サイズは元の配列の 2 倍になります;

    拡張中にサイズがまったく存在しない ThreadLocalMap はスレッドの属性であり、スレッドは同時に ThreadLocalMap でのみ操作できるため、スレッド セーフティの問題 同じスレッドによるビジネス ロジックの実行はシリアルである必要があるため、操作はThreadLocalMap もシリアルである必要があります。

    以上がJavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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