什么是ThreadLocal?
源码注释:
This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local
variable as long as the thread is alive and the {@code ThreadLocal}
instance is accessible; after a thread goes away, all of its copies of
thread-local instances are subject to garbage collection (unless other
references to these copies exist).
大致意思是:
此类提供线程局部变量。 这些变量不同于它们的普通对应变量,
因为每个访问一个(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。
ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)。
只要线程处于活动状态并且可以访问ThreadLocal实例,每个线程都持有对其线程局部变量副本的隐式引用; 线程消失后,它的所有线程本地实例副本都将进行垃圾回收(除非存在对这些副本的其他引用)。
个人理解:
用于线程见的数据隔离,所作用的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,可以防止自己的变量被其它线程修改。
底层原理
源码:
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取 ThreadLocalMap对象
if (map != null) // 验证 map 是否为空
map.set(this, value); // 不为空直接为set
else
createMap(t, value);// 为空创建一个map对象
}
======================================================
//获取 ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
// 获取当前线程的 ThreadLocalMap
return t.threadLocals;
}
======================================================
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
======================================================
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果 ThreadLocalMap 为 null 设定初始化值
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;
}
=====================================================
private Entry getEntry(ThreadLocal<?> key) {
// 获取hash值
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 判断当前位置不为空 && 是当前的 ThreadLocal,直接返回后再get方法中取出相应的值
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;
// 同 getEntry中的判断 if (e != null && e.get() == key)
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 是用于线程见的数据隔离了,每个线程都有自己独有的 t h r e a d L o c a l s \color{purple}{threadLocals} threadLocals
ThreadLocalMap底层结构
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
由源码我们可以发现,Entry 继承了 WeekReference(弱引用)
为什么Entry要使用弱引用?
如 果 是 强 引 用 , 内 存 始 终 会 被 占 用 , 始 终 不 会 被 G C 线 程 回 收 , 从 而 导 致 了 内 存 泄 漏 的 问 题 \color{red}{如果是强引用,内存始终会被占用,始终不会被GC线程回收,从而导致了内存泄漏的问题} 如果是强引用,内存始终会被占用,始终不会被GC线程回收,从而导致了内存泄漏的问题;
问题:
- 为什么要用数组?
结合实际应用,一个线程中可以同时设定多个ThreadLocal来存储不同类型的参数或对象,存放至
ThreadLocalMap t h r e a d L o c a l s \color{purple}{threadLocals} threadLocals = nulll ;
所以使用数组存储更合理 - 怎么解决Hash冲突?
源码:/** * The table, resized as necessary. * table.length MUST always be a power of two. * 表格,根据需要调整大小。 table.length 必须始终是 2 的幂 */ private Entry[] table; private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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; } // 不存在当前位置,初始化一个 Entry 放置到 i 的位置 if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
由上面源码我们可以看到每个 ThreadLocal 都会先获取对应的 Hash 值 int i = key.threadLocalHashCode & (len-1);,再定位 Entry 中的位置 如果当前位置为空,则初始化一个 Entry 存放至对应的位置(i) 如果当前位置不为空,将刷新当前位置的value
Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap中存储的Entry其中key是ThreadLocal对象本身,value则是要存储的对象
- 如何共享线程ThreadLocal数据
//使用 InheritableThreadLocal 可以实现线程间 ThreadLocal 中数据的共享
ThreadLocal threadLocal = new InheritableThreadLocal();
'demo:'
static final ThreadLocal threadLocal = new InheritableThreadLocal();
static {
threadLocal.set("2");
}
public static void main(String[] args) {
new Thread(()->{
System.out.println("1 + 1 = "+threadLocal.get());
}).start();
}
InheritableThreadLocal:输出:1 + 1 = 2
ThreadLocal:输出:1 + 1 = null
-
共享的ThreadLocal数据是怎么传递的
InheritableThreadLocal 继承于 ThreadLocal,我们在 ThreadLocal中可以看到
Thread.init()
如果 inheritThreadLocals 为 true 且 父类线程的 inheritableThreadLocals 不为 null 创建一个共享ThreadLocal的 ThreadLocalMap ,将父线程的 inheritableThreadLocals 添加至新创建的ThreadLocalMap中,再将创建好的 ThreadLocalMap 重新赋值给当前线程的 inheritThreadLocals;
这里 createInheritedMap 中用的是一个私有方法
- 内存泄漏
因为弱引用的关系,弱引用的特性这里就不再赘述了,详情可见(强、软、弱、虚引用这篇文章)。
出现场景大致为:当创建ThreadLocal的线程一直持续运行,Entry中的value就可能一直不能被回收,从而导致内存泄漏。
解决办法:在ThreadLocal使用完时调用remove方法,将当前线程创建的ThreadLocal进行清除,便于GC的回收
- 内存泄漏
实战运用:
说了这么多那么到底在什么场景下使用呢?
以Spring为例:
Spring在事物隔离级别上运用的就是 ThreadLocal 如下图所示,每个线程自己的链接都是依附于自己ThreadLocal来进行保存的
声明:此篇文章借鉴了 知乎-敖丙