ThreadLocal は、マルチスレッド環境で各スレッドが独自の一意のデータを持ち、上から下へデータを渡すことができるようにする方法を提供します。スレッドの実行全体にわたって。
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 を見てください。基礎となる層がコンテキスト転送を実装する方法。
ThreadLocal はジェネリックスを使用してクラスを定義し、ThreadLocal が任意の形式でデータを保存できることを示します。ソース コードは次のとおりです。
public class ThreadLocal<T> {}
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 を管理することです。
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 の値です。
ThreadLocal はスレッドセーフであり、主に ThreadLocalMap がスレッドの属性であるため、安心して使用できます。スレッド Thread で排他的に分離されたソース コード。
親スレッドが子スレッドを作成すると、inheritableThreadLocals の値はコピーされますが、threadLocals の値はコピーされません。ソース コードは次のとおりです:
上から この図では、スレッドが作成されると、親スレッドの継承可能なThreadLocals属性値がコピーされることがわかります。
4. Set メソッド
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(); }
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 サイトの他の関連記事を参照してください。