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 の値はコピーされません。ソース コードは次のとおりです:
上から この図では、スレッドが作成されると、親スレッドの継承可能なThreadLocals属性値がコピーされることがわかります。
4. Set メソッド
// 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); }
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 のハッシュコードとして使用されます; 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(); }
- 式配列のインデックス位置を計算するためのハッシュコードは、配列サイズを法としたハッシュコードです。ハッシュコードは増加し続けるため、異なるハッシュコードが同じ配列のインデックス位置を計算する可能性が高くなります(実際のプロジェクトではこれを気にしないでください) , ThreadLocal は非常に少なく、基本的に競合はありません);
- hashCode によって計算インデックス位置 i にすでに値がある場合は、i から開始して検索を続けます空のインデックス位置が見つかるまで 1 から逆方向に進み、現在の ThreadLocal をキーとして置きます。
- 幸いなことに、日常業務で ThreadLocal を使用する場合、ThreadLocal は 1 ~ 2 つしか使用しないことが多く、ハッシュを介して重複した配列を計算する確率はそれほど高くありません。 set 中の配列要素の位置の競合を解決する戦略は、get メソッドにも影響します。get メソッドについても一緒に見てみましょう。
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); }
// 自旋 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; }
以上がJavaプログラミングにおけるThreadLocalの詳細な説明とソースコード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

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

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

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

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

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

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

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

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

ホットトピック









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

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

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

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

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

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