千家信息网

如何从面试中的问题分析ThreadLocal

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章给大家介绍如何从面试中的问题分析ThreadLocal,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。ThreadLocal是什么ThreadLocal是一个本地线程副本变
千家信息网最后更新 2026年11月02日如何从面试中的问题分析ThreadLocal

这篇文章给大家介绍如何从面试中的问题分析ThreadLocal,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

ThreadLocal是什么

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

从数据结构入手

下图为ThreadLocal的内部结构图

从上面的结构图,我们已经窥见ThreadLocal的核心机制:

每个Thread线程内部都有一个Map。 Map里面存储线程本地对象(key)和线程的变量副本(value) 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

Thread线程内部的Map在类中描述如下:

public class Thread implements Runnable {/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;}

深入解析ThreadLocal

ThreadLocal类提供如下几个核心方法:

public T get()public void set(T value)public void remove()

get()方法用于获取当前线程的副本变量值。 set()方法用于保存当前线程的副本变量值。 initialValue()为当前线程初始副本变量值。 remove()方法移除当前前程的副本变量值。

get()方法

/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/public T get() {Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)return (T)e.value;

}return setInitialValue();

}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);

elsecreateMap(t, value);return value;}protected T initialValue() {return null;}

步骤:

1.获取当前线程的ThreadLocalMap对象threadLocals

2.从map中获取线程存储的K-V Entry节点。

3.从Entry节点获取存储的Value副本值返回。

4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException。

set()方法

/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of* this thread-local.*/public void set(T value) {Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)map.set(this, value);

elsecreateMap(t, value);

}ThreadLocalMap getMap(Thread t) {return t.threadLocals;

}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);

}

步骤:

1.获取当前线程的成员变量map

2.map非空,则重新将ThreadLocal和新的value副本放入到map中。

3.map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中。

remove()方法

/*** Removes the current thread's value for this thread-local* variable. If this thread-local variable is subsequently* {@linkplain #get read} by the current thread, its value will be* reinitialized by invoking its {@link #initialValue} method,* unless its value is {

@linkplain #set set} by the current thread* in the interim.

This may result in multiple invocations of the* initialValue method in the current thread.** @since 1.5*/public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());

if (m != null)m.remove(this);

}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

remove方法比较简单,不做赘述。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。

在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。

static class Entry extends WeakReference {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal k, Object v) {super(k);value = v;}}

Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。

ThreadLocalMap的成员变量:

static class ThreadLocalMap {/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** The number of entries in the table.*/private int size = 0;/*** The next size value at which to resize.*/private int threshold; // Default to 0}

Hash冲突怎么解决

和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

/*** Increment i modulo len.*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** Decrement i modulo len.*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}

显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。

所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

ThreadLocalMap的问题

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何避免泄漏

既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

ThreadLocal threadLocal = new ThreadLocal();try {threadLocal.set(new Session(1, "Misout的博客"));// 其它业务逻辑} finally {threadLocal.remove();}

应用场景

还记得Hibernate的session获取场景吗?

private static final ThreadLocal threadLocal = new ThreadLocal();//获取Sessionpublic static Session getCurrentSession(){Session session = threadLocal.get();//判断Session是否为空,如果为空,将创建一个session,并设置到本地线程变量中try {if(session ==null&&!session.isOpen()){if(sessionFactory==null){rbuildSessionFactory();// 创建Hibernate的SessionFactory}else{session = sessionFactory.openSession();}}threadLocal.set(session);} catch (Exception e) {// TODO: handle exception}return session;}

为什么?每个线程访问数据库都应当是一个独立的Session会话,如果多个线程共享同一个Session会话,有可能其他线程关闭连接了,当前线程再执行提交时就会出现会话已关闭的异常,导致系统异常。此方式能避免线程争抢Session,提高并发下的安全性。

使用ThreadLocal的典型场景正如上面的数据库连接管理,线程会话管理等场景,只适用于独立变量副本的情况,如果变量为全局共享的,则不适用在高并发下使用。

关于如何从面试中的问题分析ThreadLocal就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

线程 副本 方法 变量 对象 方式 冲突 场景 量值 结构 问题 位置 多个 数据 独立 分析 不同 就是 成员 线性 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 山东特亿宝互联网科技有限公司 互联网科技金融公司 互联网 科技创业 为想互联网科技 礼当家互联网科技 互联网科技公司官网 紫鲸互联网科技 互联网与未来科技 深圳天众互联网科技公司 伴你行互联网科技有限公司 互联网高科技企业 互联网科技公司介绍 甘肃拓世互联网科技有限公司 成都玖崟互联网科技有限公司乐商圈怎么样 世界互联网科技公司市值排名 企晟互联网信息科技有限公司 上海京穆互联网科技有限公司 互联网领先科技宣传片 美国五大互联网科技公司市值多少 利用互联网等科技手段 亿人帮互联网科技有限公司 互联网科技有限公司经营范围 科技与互联网发展 互联网金融科技委员会 互联网科技类展会 以后互联网后是啥科技 受益于互联网科技的高速发展 佛山市拉法互联网科技有限公司 济南小医互联网科技有限公司招聘 互联网科技创新创业项目
0