目次
はじめに
1. 使い方のデモ
2. クラス構造
2.1. クラス ジェネリックス
2.2. 主要な属性
2.2.1、ThreadLocalMap
3. ThreadLocal はスレッド間のデータ分離をどのように実現しますか?
ホームページ Java &#&チュートリアル JavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析

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

Apr 21, 2023 pm 03:19 PM
java threadlocal

    はじめに

    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 サイトの他の関連記事を参照してください。

    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

    ホットAIツール

    Undresser.AI Undress

    Undresser.AI Undress

    リアルなヌード写真を作成する AI 搭載アプリ

    AI Clothes Remover

    AI Clothes Remover

    写真から衣服を削除するオンライン AI ツール。

    Undress AI Tool

    Undress AI Tool

    脱衣画像を無料で

    Clothoff.io

    Clothoff.io

    AI衣類リムーバー

    AI Hentai Generator

    AI Hentai Generator

    AIヘンタイを無料で生成します。

    ホットツール

    メモ帳++7.3.1

    メモ帳++7.3.1

    使いやすく無料のコードエディター

    SublimeText3 中国語版

    SublimeText3 中国語版

    中国語版、とても使いやすい

    ゼンドスタジオ 13.0.1

    ゼンドスタジオ 13.0.1

    強力な PHP 統合開発環境

    ドリームウィーバー CS6

    ドリームウィーバー CS6

    ビジュアル Web 開発ツール

    SublimeText3 Mac版

    SublimeText3 Mac版

    神レベルのコード編集ソフト(SublimeText3)

    Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

    Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

    Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

    Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

    ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

    Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

    Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

    Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

    Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

    この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

    Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

    Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

    Java での日付までのタイムスタンプ Java での日付までのタイムスタンプ Aug 30, 2024 pm 04:28 PM

    Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

    カプセルの量を見つけるためのJavaプログラム カプセルの量を見つけるためのJavaプログラム Feb 07, 2025 am 11:37 AM

    カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

    See all articles