ThreadLocal 线程本地变量的使用
ThreadLocal是什么
顾名思义,指的是线程私有的变量,同一个对象,在不同线程中调用相同方法拿到的数据相互独立,我们先代码实验一下效果。
测试代码
public class ThreadLocalTest {
private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
public static void test() {
stringThreadLocal.set("initial");
long currentTid0 = Thread.currentThread().getId();
Logger.i("main thread currentId0: " + currentTid0 + ", stringThreadLocal: " + stringThreadLocal.get());
WorkerThread.getInstance().submit(()->{
long currentTid = Thread.currentThread().getId();
Logger.i("outer thread currentId: " + currentTid + ", stringThreadLocal: " + stringThreadLocal.get());
stringThreadLocal.set("thread: " + currentTid);
WorkerThread.getInstance().submit(()->{
long currentTid1 = Thread.currentThread().getId();
Logger.i("in new thread currentId1: " + currentTid1 + ", stringThreadLocal: " + stringThreadLocal.get());
stringThreadLocal.set("thread: " + currentTid1);
});
});
输出为:
debug: main thread currentId0: 1, stringThreadLocal: initial
debug: outer thread currentId: 16, stringThreadLocal: null
debug: in new thread currentId1: 18, stringThreadLocal: null
可以看出,不同线程拿到的数据是不一样的!!!
我们知道,java堆中的对象是同进程的线程共享的;那么这个ThreadLocal 能够实现线程私有,它是怎么实现的呢?
从源码中找答案
- 先看ThreadLocal.java 中关于set 的实现
//ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
//获取属于当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);//如果map未创建,则创建新的
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
// 以当前ThreadLocal对象为参数传入构建ThreadLocalMap,赋值给线程的成员变量
}
// 构造ThreadLocalMap对象
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue); //key继续传入Entry结构
size = 1;
setThreshold(INITIAL_CAPACITY);
}
- 然后继续看Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);// 实际上,传入进来的ThreadLocal对象是以弱引用的方式被引用的,是为了避免内存泄漏
value = v;
}
}
- 我们接着看get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //拿到当前线程的map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //拿到entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// 我们再看下是怎么getEntry的
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))// 看entry是否引用了key,这就和构造时对应起来了
return e;
else
return getEntryAfterMiss(key, i, e);
}
总结
ThreadLocal 能够实现同一个对象,在不同的线程有相互独立的值是因为相关线程对象Thread中有自己的私有变量ThreadLocalMap,每个线程调用set/put 访问变量时,实际上会先找到当前线程的ThreadLocalMap,然后从里面取,所以能够做到线程间相互独立。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构