ThreadLocal:线程局部变量
跳到导航
跳到搜索
关于
ThreadLocal 关键字的概念与作用,在“Java 核心技术”(卷 I)的“并发”部分[1]与“深入理解 JVM 虚拟机”的“线程安全”部分[2]已经了解差不多了。 简单来说:ThreadLocal 就是通过定义“线程局部变量”的方式实现“线程本地存储”。 其作用(或者说应用场景)[3]: 1、【变量隔离】为变量在每个线程中创建一个副本,避免同一参数在所有线程中传递。 2、【变量传递】为线程开辟独立的存储空间,用于在同一线程的流程中保持上下文。
源码相关
总结:
- Thread 类:
- 属性:
threadLocals
(类型:ThreadLocal.ThreadLocalMap)用于维护“线程本地变量”。
- 属性:
- ThreadLocal 类:当前线程的 ThreadLocalMap 的访问入口
- ThreadLocal 对象通常被定义为“private static”
- 属性:
- 每个 ThreadLocal 对象都包含了一个独一无二的
threadLocalHashCode
; ——【用于计算 ThreadLocal 对象在 ThreadLocalMap.table 中的位置】
- 每个 ThreadLocal 对象都包含了一个独一无二的
- 方法:
set(T value)
:设置“线程本地变量”的值,将调用 ThreadLocalMap 的set(ThreadLocal<?> key, Object value)
;T get()
:获取“线程本地变量”的值,将调用 ThreadLocalMap 的getEntry(ThreadLocal<?> key)
;remove()
[4]:删除此“线程本地变量”,将调用 ThreadLocalMap 的remove(ThreadLocal<?> key)
;
- 【泛型类,所以“线程本地变量”的值可以为各种类型】
- ThreadLocalMap 类[5]:存储了一组“K-V”映射(其实质是一个 Entry 数组)
- ThreadLocal 的内部类;
- 属性:
private Entry[] table;
:Entry 数组,用于存储“K-V”映射。【key:“ThreadLocal对象”; value:“线程本地变量的值”】
- 方法:
set(ThreadLocal<?> key, Object value)
:向 table 中添加/设置 Entry;getEntry(ThreadLocal<?> key)
:从 table 中获取“ThreadLocal 对象”关联的 Entry;remove(ThreadLocal<?> key)
:从 table 中移除“ThreadLocal 对象”关联的 Entry;
- Entry 类:一个 Entry 就是一个“线程本地变量 -> 线程本地变量的值”的映射
- ThreadLocalMap 的内部类,继承于:“WeakReference”[6];
- 属性:
referent
:(从 Reference 类中继承而来)保存“ThreadLocal对象”value
:保存“线程本地变量的值”
源码
- Thread:
package java.lang; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import ... public class Thread implements Runnable { ... /* * 与此线程相关的 ThreadLocal 值。此映射由【ThreadLocal 类】维护。 */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * 与此线程相关的 InheritableThreadLocal 值。此映射由【InheritableThreadLocal 类】维护。 */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ... }
- threadLocals:保存线程的“线程本地变量”。
- inheritableThreadLocals:保存线程从父线程继承的“线程本地变量”。
- InheritableThreadLocal 类:(继承了 ThreadLocal 类)提供从父线程到子线程的值继承:当创建子线程时,子线程接收父线程具有值的所有可继承的“线程本地变量”的初始值。
- 通常情况下,孩子的值与父母的值相同;但是,通过重写该类中的“childValue”方法,可以使子级的值成为父级的任意函数。
- InheritableThreadLocal 类:(继承了 ThreadLocal 类)提供从父线程到子线程的值继承:当创建子线程时,子线程接收父线程具有值的所有可继承的“线程本地变量”的初始值。
- ThreadLocal:
package java.lang; import java.lang.ref.*; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; /** * 此类提供【“线程局部变量”】。 * 这些变量与它们的正常对应变量不同,因为每个访问一个变量的线程(通过其{@code get}或{@code set}方法)都有自己的、独立初始化的变量副本。 * * @since 1.2 */ public class ThreadLocal<T> { /** * ThreadLocals 依赖于每个线程的 ThreadLocal.ThreadLocalMap(Thread.threadLocals 和 Thread.inheritableThreadLocals)。 * ThreadLocal.ThreadLocalMap 中: * ThreadLocal对象充当键,通过 threadLocalHashCode 进行搜索。 * * 这是一个自定义哈希代码(仅在ThreadLocalMaps中有用): * 它可以在相同线程使用连续构造的ThreadLocals的常见情况下消除冲突, * 而在不太常见的情况下保持良好的行为。 */ private final int threadLocalHashCode = nextHashCode(); // 要给出的下一个哈希代码(从零开始,自动更新) private static AtomicInteger nextHashCode = new AtomicInteger(); // 连续生成的哈希代码之间的差值 private static final int HASH_INCREMENT = 0x61c88647; /** * 返回下一个哈希代码 */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } ... /** * 【获取:当前线程的“局部变量”(此 ThreadLocal)】 */ public T get() { // 获取当前线程的 ThreadLocalMap Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); /* * 如果 ThreadLocalMap 不为空: * 获取“此 ThreadLocal”关联的 Entry 的值 */ if (map != null) { // 从 ThreadLocalMap 中查找“此 ThreadLocal”关联的 Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { // 获取 Entry 的值(即“此 ThreadLocal”的值) @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } /* * 如果 ThreadLocalMap 为空: * 使用“初始值”来为当前线程创建 ThreadLocalMap */ return setInitialValue(); } /** * 【设置:当前线程的“局部变量”(此 ThreadLocal)】 */ public void set(T value) { // 获取当前线程的 ThreadLocalMap Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) /* * 如果“线程的 ThreadLocalMap”不为空: * 向其中设置 Entry(关联“此 ThreadLocal”、值为 value) */ map.set(this, value); else /* * 如果 ThreadLocalMap 为空: * 使用 value 来为当前线程创建 ThreadLocalMap */ createMap(t, value); } /** * 【移除:当前线程的“局部变量”(此 ThreadLocal)】 */ public void remove() { // 获取当前线程的 ThreadLocalMap ThreadLocalMap m = getMap(Thread.currentThread()); /* * 如果 ThreadLocalMap 不为空: * 移除“此 ThreadLocal”关联的 Entry */ if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ... /** * 内部类:【ThreadLocal.ThreadLocalMap】 * * ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。在ThreadLocal类之外不导出任何操作。 * 该类是包私有的,以允许在Thread类中声明字段。 * 为了帮助处理非常大且长期的使用,哈希表条目使用WeakReferences作为键。 * 但是,由于不使用引用队列,只有当表空间开始耗尽时,才保证删除过时的条目。 */ static class ThreadLocalMap { /** * 内部类:【ThreadLocal.ThreadLocalMap.Entry】 * 此哈希映射中的条目扩展 WeakReference,使用其主ref字段作为键(始终是ThreadLocal对象)。 * * 注意,空键(即entry.get()==null)意味着不再引用该键,所以可以从表中删除该项。 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } // 初始容量 —— 必须是2的幂。 private static final int INITIAL_CAPACITY = 16; // 表,根据需要调整大小。table.length 必须始终是2的幂。 private Entry[] table; // 表中的条目数。 private int size = 0; // 阈值:用于调整大小的下一个size值。 private int threshold; // Default to 0 /** * 设置调整大小的阈值,以最坏情况下保持2/3的负载系数。 */ private void setThreshold(int len) { threshold = len * 2 / 3; } ... /** * 构造一个新映射:包含最初的(firstKey,firstValue)。 * * ThreadLocalMaps是延迟构建的,因此我们只有在至少有一个条目可以放入其中时才创建一个。 */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * 构造一个新映射:包括给定父映射中的所有可继承ThreadLocal。 * * 仅由createInheritedMap调用。 */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } /** * 【获取与 ThreadLocal 关联的 Entry】 * * 此方法本身仅处理快速路径: * 直接命中现有键。否则它将中继到 getEntryAfterMiss。 * 这是为了最大限度地提高直接命中的性能,部分原因是使该方法易于内联。 */ private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } /** * 在其直接哈希槽中找不到键时使用的 getEntry 方法的版本。 */ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) // 清除过时的条目 expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } /** * 【添加与 ThreadLocal 关联的 Entry】 */ private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; 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; } if (k == null) { // 将 set 操作期间遇到的过时项替换为指定 key 的项 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; // 试探性地扫描一些单元格以查找过时的条目。 if (!cleanSomeSlots(i, sz) && sz >= threshold) // 重新包装 和/或 调整 table 大小。 rehash(); } /** * 【移除与 ThreadLocal 关联的 Entry】 */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); // 清除过时的条目 expungeStaleEntry(i); return; } } } ... } }
- ThreadLocal 实例通常是希望将状态与线程关联的类中的“私有静态”(private static)字段(例如,用户ID 或 事务ID):
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
- 如上,类生成每个线程本地的唯一标识符。线程的id在第一次调用{@code ThreadId.get()}时被分配,并且在后续调用时保持不变。
- 只要线程处于活动状态且 ThreadLocal 实例可访问,每个线程都保持对其线程本地变量副本的隐式引用;当一个线程离开后,它的所有线程本地实例副本都将被垃圾收集(除非存在对这些副本的其他引用)。
应用过程
参考
- ↑ 参考:【核心技术:并发#线程局部变量(ThreadLocal)】
- ↑ 参考:【深入理解JVM:线程安全与锁优化#无同步方案】
- ↑
关于 ThreadLocal 的作用:
- 【变量隔离】:就是用于实现“线程安全”。
- 【变量传递】:参考【Hibernate笔记_4:核心知识#关于“Session_管理方式:绑定到本地线程”】(将业务层获取的 Session 传递到 DAO 层)。
- ↑
为什么要求“ThreadLocalMap 对象使用完毕后及时调用 remove()?”:
- 在使用线程池的时候,线程执行完毕放回线程池中,有可能在下一次执行时带入上一次的执行状态。
- 便于对线程的 ThreadLocalMap 对象的 table 及时清理,以释放内存。
- ↑
ThreadLocalMap 对象的清理:
- ThreadLocalMap 的“get、set、remove”等方法中,会调用“expungeStaleEntry、replaceStaleEntry、cleanSomeSlots”等方法(根据“
if (e != null && e.get() == null)
”判断 Entry 所引用的 ThreadLocal 对象是否还存在)来对 table 进行清理,以及时释放内存。
- ThreadLocalMap 的“get、set、remove”等方法中,会调用“expungeStaleEntry、replaceStaleEntry、cleanSomeSlots”等方法(根据“
- ↑
关于
Entry extends WeakReference<ThreadLocal<?>>
:WeakReference 标志性的特点是:
不会影响到被应用对象的 GC 回收行为(即,只要对象被除 WeakReference 对象之外所有的对象解除引用后,该对象便可以被 GC 回收),
只不过在被对象回收之后,WeakReference 实例获取被引用对象时会返回 null。- 为什么 Entry 继承 WeakReference?
- ThreadLocal 对象的 GC 回收:如果使用“强引用”,ThreadLocal 对象在使用完毕,将会由于被 Entry 对象“强引用”所引用导致无法被 GC 回收。
- 为什么 Entry 继承 WeakReference?