ThreadLocal
基本介绍
ThreadLocal 是 JDK 提供的一个工具类,用于线程内部的数据隔离。每个线程通过它都可以拥有属于自己的变量副本,从而实现线程间的数据不共享。
本质:是一个 Map,结构类似于:
1 2 3 4 5
| Thread { ThreadLocalMap { ThreadLocal<Object>, value } }
|
ThreadLocal 实例通常来说都是 private static 类型的,属于一个线程的本地变量,用于关联线程和线程上下文。每个线程内部都维护着一个自己的 ThreadLocalMap,这个 Map 存的是当前线程所持有的所有 ThreadLocal 变量。
ThreadLocal 作用:
- 线程并发:应用在多线程并发的场景下
- 传递数据:通过 ThreadLocal 实现在同一线程不同函数或组件中传递公共变量,减少传递复杂度
- 线程隔离:每个线程的变量都是独立的,不会互相影响
| 场景 |
解释 |
| 线程隔离 |
每个线程数据独立,互不干扰 |
| 数据传递 |
在同一线程中跨方法或组件共享数据,避免参数传递 |
| 事务管理、连接池、工具封装 |
JDBC连接、日期解析等 |
对比 synchronized:
| 对比项 |
synchronized |
ThreadLocal |
| 思路 |
以时间换空间,多线程排队使用共享变量 |
以空间换时间,每个线程一份变量副本 |
| 解决问题 |
多线程访问共享资源的同步问题 |
多线程之间变量相互隔离问题 |
| 适用场景 |
并发读写同一个资源 |
每个线程需要独立使用变量 |
基本使用
常用方法
| 方法 |
说明 |
set(T value) |
设置当前线程变量的值 |
get() |
获取当前线程变量的值 |
remove() |
移除当前线程变量,防止内存泄漏 |
initialValue()(可重写) |
设置初始值 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class MyDemo {
private static ThreadLocal<String> tl = new ThreadLocal<>();
private String content;
private String getContent() { return tl.get(); }
private void setContent(String content) { tl.set(content); }
public static void main(String[] args) { MyDemo demo = new MyDemo(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { demo.setContent(Thread.currentThread().getName() + "的数据"); System.out.println("-----------------------"); System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent()); } }); thread.setName("线程" + i); thread.start(); } } }
|

应用场景
ThreadLocal 适用于下面两种场景:
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享
ThreadLocal 方案有两个突出的优势:
- 传递数据:保存每个线程绑定的数据,在需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题
- 线程隔离:各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失
ThreadLocal 用于数据连接的事务管理:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class JdbcUtils { private static final ThreadLocal<Connection> tl = new ThreadLocal(); private static final ComboPooledDataSource ds = new ComboPooledDataSource(); public static Connection getConnection() throws SQLException { Connection conn = tl.get(); if (conn == null) { conn = ds.getConnection(); tl.set(conn); } return conn; } }
|
用 ThreadLocal 使 SimpleDateFormat 从独享变量变成单个线程变量:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ThreadLocalDateUtil { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } };
public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); }
public static String format(Date date) { return threadLocal.get().format(date); } }
|

实现原理
ThreadLocal 的设计目的
ThreadLocal 的核心作用是:为每个线程提供变量副本,避免共享冲突,实现线程隔离。
举个形象的比喻:每个线程像一个学生,ThreadLocal 像是发下去的作业纸,每个人写自己的,不会互相干扰。
底层结构演变
JDK8 以前:每个 ThreadLocal 都创建一个 Map,然后用线程作为 Map 的 key,要存储的局部变量作为 Map 的 value,达到各个线程的局部变量隔离的效果。这种结构会造成 Map 结构过大和内存泄露,因为 Thread 停止后无法通过 key 删除对应的数据


JDK8 以后:每个 Thread 维护一个 ThreadLocalMap,这个 Map 的 key 是 ThreadLocal 实例本身,value 是真正要存储的值
- 每个 Thread 线程内部都有一个 Map (ThreadLocalMap)
- Map 里面存储 ThreadLocal 对象(key)和线程的私有变量(value)
- Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值
- 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成副本的隔离,互不干扰


JDK8 前后对比:
- 每个 Map 存储的 Entry 数量会变少,因为之前的存储数量由 Thread 的数量决定,现在由 ThreadLocal 的数量决定,在实际编程当中,往往 ThreadLocal 的数量要少于 Thread 的数量
- 当 Thread 销毁之后,对应的 ThreadLocalMap 也会随之销毁,能减少内存的使用,防止内存泄露
总结对比(JDK8 前后)
| 对比项 |
JDK8 之前 |
JDK8 之后 |
| Map 所属 |
每个 ThreadLocal 拥有一个 Map |
每个 Thread 拥有一个 Map |
| Map key |
线程对象(Thread) |
ThreadLocal 实例 |
| Map value |
局部变量 |
局部变量 |
| 内存占用 |
较高 |
更节省内存 |
| 是否易泄漏 |
是 |
相对更安全(但仍需手动 remove) |

这部分详见JVM面试篇


成员变量
1.每个线程维护自己的 ThreadLocalMap
1
| ThreadLocal.ThreadLocalMap threadLocals = null
|
- 每个线程对象中都维护着一个 ThreadLocalMap,用于存放线程自己的 ThreadLocal 值。
- Map 的 key 是 ThreadLocal 对象,value 是线程局部变量值。
2.threadLocalHashCode:哈希值
1
| private final int threadLocalHashCode = nextHashCode()
|
- 每一个 ThreadLocal 对象实例都有一个唯一的 hash 值,决定它在 ThreadLocalMap 中的位置。
- 使用了斐波那契散列算法,能让哈希值均匀分布,减少哈希冲突。
1
| private static final int HASH_INCREMENT = 0x61c88647;
|
这个常量是黄金比例因子,在 Java 中被广泛用于哈希均衡(如 ThreadLocal、ConcurrentHashMap)。
成员方法
方法都是线程安全的,因为 ThreadLocal 属于一个线程的,ThreadLocal 中的方法,逻辑都是获取当前线程维护的 ThreadLocalMap 对象,然后进行数据的增删改查,没有指定初始值的 threadlcoal 对象默认赋值为 null
- initialValue():返回该线程局部变量的初始值
- 延迟调用的方法,在执行 get 方法时才执行该方法缺省(默认)实现直接返回一个 null如果想要一个初始值,可以重写此方法, 该方法是一个
protected 的方法,为了让子类覆盖而设计的
1 2 3
| protected T initialValue() { return null; }
|
1 2 3 4
| private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
|
- set(T value):修改当前线程与当前 threadlocal 对象相关联的线程局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
|
解读:
-
获取当前线程的 ThreadLocalMap
-
如果已存在,就往 Map 中设置 this(当前 ThreadLocal 对象)为 key 的值
-
如果还没有初始化 Map,就调用 createMap() 创建一个新的并设置值
-
get():获取当前线程与当前 ThreadLocal 对象相关联的线程局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } }
return setInitialValue(); } 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; }
|
解读:
-
尝试从当前线程中拿到与当前 ThreadLocal 对象关联的值
-
如果找不到,调用 initialValue() 获取默认值,并设置进去
-
remove():移除当前线程与当前 threadLocal 对象相关联的线程局部变量
1 2 3 4 5 6 7
| public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
|
解读:
- 移除当前线程中与当前 ThreadLocal 绑定的那一项。
- **非常关键!**线程池环境中若不及时移除,可能造成内存泄漏或数据污染。
小结


ThreadLocalMap
ThreadLocalMap 的结构核心
ThreadLocalMap 是 ThreadLocal 的静态内部类,它本质上是一个定制版哈希表(数组+线性探测)。
成员属性
主要属性
| 属性名 |
作用说明 |
INITIAL_CAPACITY |
初始容量(固定为 16) |
Entry[] table |
存储数据的表,数组结构 |
int size |
当前存储元素数量 |
int threshold |
扩容阈值(2/3 表容量) |
ThreadLocalMap 是 ThreadLocal 的内部类,没有实现 Map 接口,用独立的方式实现了 Map 的功能,其内部 Entry 也是独立实现
1 2 3 4 5 6 7 8 9 10 11
| private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold;
|
存储结构 Entry:

- Entry 继承 WeakReference,key 是弱引用,目的是将 ThreadLocal 对象的生命周期和线程生命周期解绑
- Entry 限制只能用 ThreadLocal 作为 key,key 为 null (entry.get() == null) 意味着 key 不再被引用,entry 也可以从 table 中清除
1 2 3 4 5 6 7 8
| static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
|
构造方法:延迟初始化的,线程第一次存储 threadLocal - value 时才会创建 threadLocalMap 对象

1 2 3 4 5 6 7 8 9 10 11 12
| 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); }
|
成员方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| private void set(ThreadLocal<?> key, Object value) { ThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (ThreadLocal.ThreadLocalMap.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) { replaceStaleEntry(key, value, i); return; } }
tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e;
int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i;
for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get();
if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e;
if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; }
if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; }
tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); }
|

- getEntry():ThreadLocal 的 get 方法以当前的 ThreadLocal 为 key,调用 getEntry 获取对应的存储实体 e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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); }
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; }
|

- rehash():触发一次全量清理,如果数组长度大于等于长度的
2/3 * 3/4 = 1/2,则进行 resize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private void rehash() { expungeStaleEntries(); if (size >= threshold - threshold / 4) resize(); }private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } }
|
Entry 数组为扩容为原来的 2 倍 ,重新计算 key 的散列值,如果遇到 key 为 null 的情况,会将其 value 也置为 null,帮助 GC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0;
for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } }
setThreshold(newLen); size = count; table = newTab; }
|

remove():删除 Entry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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)]) { // 找到了对应的 key if (e.get() == key) { // 设置 key 为 null e.clear(); // 探测式清理 expungeStaleEntry(i); return; } } }
|

重要机制总结
| 机制 |
说明 |
| 线性探测 |
哈希冲突时顺序查找下一个空槽 |
| 过期数据处理 |
使用 replaceStaleEntry + cleanSomeSlots 清理 |
| 弱引用 Entry |
ThreadLocal 不在引用时能被回收 |
| 自动扩容 |
达到阈值时触发 rehash & resize |
| 内存泄漏预防 |
明确调用 remove() 或手动清理 stale Entry |
ThreadLocalMap的清理机制
- 探测式清理:沿着开始位置向后探测清理过期数据,沿途中碰到未过期数据则将此数据 rehash 在 table 数组中的定位,重定位后的元素理论上更接近
i = entry.key & (table.length - 1),让数据的排列更紧凑,会优化整个散列表查询性能

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length;
tab[staleSlot].value = null; tab[staleSlot] = null; size--;
Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
|


- 启发式清理:向后循环扫描过期数据,发现过期数据调用探测式清理方法,如果连续几次的循环都没有发现过期数据,就停止扫描

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ((n >>>= 1) != 0); return removed; }
|
内存泄漏
Java 中的内存泄漏是指:对象本该被回收,却因为仍然存在强引用链而无法释放。
- 如果 key 使用强引用:使用完 ThreadLocal ,threadLocal Ref 被回收,但是 threadLocalMap 的 Entry 强引用了 threadLocal,造成 threadLocal 无法被回收,无法完全避免内存泄漏

- 如果 key 使用弱引用:使用完 ThreadLocal ,threadLocal Ref 被回收,ThreadLocalMap 只持有 ThreadLocal 的弱引用,所以threadlocal 也可以被回收,此时 Entry 中的 key = null。但没有手动删除这个 Entry 或者 CurrentThread 依然运行,依然存在强引用链,value 不会被回收,而这块 value 永远不会被访问到,也会导致 value 内存泄漏

- 两个主要原因:
- 没有手动删除这个 Entry
- CurrentThread 依然运行


根本原因:ThreadLocalMap 是 Thread的一个属性,生命周期跟 Thread 一样长,如果没有手动删除对应 Entry 就会导致内存泄漏
解决方法:使用完 ThreadLocal 中存储的内容后将它 remove 掉就可以

ThreadLocal 内部解决方法:在 ThreadLocalMap 中的 set/getEntry 方法中,通过线性探测法对 key 进行判断,如果 key 为 null(ThreadLocal 为 null)会对 Entry 进行垃圾回收。所以使用弱引用比强引用多一层保障,就算不调用 remove,也有机会进行 GC


ThreadLocalMap 的变量传递问题
为什么普通 ThreadLocal 无法跨线程传递?
ThreadLocal 是线程本地存储机制,每个线程维护一份自己的副本:
1
| Thread --> ThreadLocalMap --> Entry(ThreadLocal, value)
|
每个线程的 ThreadLocalMap 是隔离的,互不访问。因此,普通 ThreadLocal 无法跨线程传递变量。
如何实现变量传递?使用 InheritableThreadLocal
父子线程:创建子线程的线程是父线程,比如实例中的 main 线程就是父线程
ThreadLocal 中存储的是线程的局部变量,如果想实现线程间局部变量传递可以使用 InheritableThreadLocal 类
1 2 3 4 5 6 7
| public static void main(String[] args) { ThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); threadLocal.set("父线程设置的值");
new Thread(() -> System.out.println("子线程输出:" + threadLocal.get())).start(); } // 子线程输出:父线程设置的值
|
这个类自动完成了 ThreadLocalMap 的复制操作,让子线程拥有与父线程一样的副本。
底层原理解析
InheritableThreadLocal 源码

1 2 3 4 5 6 7 8 9 10 11
| public class InheritableThreadLocal<T> extends ThreadLocal<T> { protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
|
实现父子线程间的局部变量共享需要追溯到 Thread 对象的构造方法

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null) { this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } }
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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) { 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 |
InheritableThreadLocal |
| 数据隔离性 |
每线程独立 |
每线程独立 |
| 支持传递 |
❌ 不支持跨线程 |
✅ 支持从父线程传递 |
| 使用场景 |
一般线程变量隔离 |
子线程需继承父变量 |
| 能否自定义传递值 |
❌ |
✅ 可重写 childValue() |
| 示例应用 |
用户 ID、请求 ID 等 |
Web 请求上下文、日志追踪 |

