java线程本地存储_深入理解Java本地线程变量ThreadLocal

论坛 期权论坛 编程之家     
选择匿名的用户   2021-6-2 17:55   1705   0

bc7cd5527ae277cffe6aed3951aacd8a.png

ThreadLocal是什么?

ThreadLocal字面意思是本地线程,其实更准确来说是线程局部变量,线程类Thread有个变量叫做threadLocals,他的类型就是ThreadLocal.ThreadLocalMap类型,其实它就是一个map类型,key是当前线程的ThreadLocal对象,值就是你要保存的数据。

ThreadLocal有什么用?

我们知道,在多线程并发执行时,一方面,需要进行数据共享,于是才有了volatile变量解决多线程间的数据可见性,也有了锁的同步机制,使变量或代码块在某一时该,只能被一个线程访问,确保数据共享的正确性。其中,Synchronized用于线程间的数据共享的。另一方面,并不是所有数据都需要共享的,这些不需要共享的数据,让每个线程单独去维护就行了,ThreadLocal就是用于线程间的数据隔离的。

ThreadLocal类的源码

我们先来看看ThreadLocal类是如何为每个线程创建一个变量的副本的。

首先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.

*/

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

}

}

return setInitialValue();

}

第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocal.ThreadLocalMap。

然后接着下面获取到Entry键值对,注意这里获取Entry时参数传进去的是this,即ThreadLocal实例,而不是当前线程t。如果获取成功,则返回value值。

如果map为空,则调用setInitialValue方法返回一个初始value,其实这个默认初始value为null。

接着来看一下getMap方法做了什么:

/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

/**

* Get the map associated with a ThreadLocal. Overridden in

* InheritableThreadLocal.

*

* @return the map

*/

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals,类型为ThreadLocal.ThreadLocalMap。就是上面提到的每一个线程都自带一个ThreadLocalMap成员变量。

继续来看ThreadLocalMap的实现:

static class ThreadLocalMap {

/**

* its main ref field as the key (which is always a

* ThreadLocal object). Note that null keys (i.e. entry.get()

* == null) mean that the key is no longer referenced, so the

* entry can be expunged from table. Such entries are referred to

* as "stale entries" in the code that follows.

*/

static class Entry extends WeakReference> {

/** The value associated with this ThreadLocal. */

Object value;

Entry(ThreadLocal> k, Object v) {

super(k);

value = v;

}

}

总结一下

在每个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals,这个ThreadLocalMap成员变量的Entry的Key为,当前ThreadLocal变量的WeakReference封装,value为要存储的变量。

为何ThreadLocalMap的键值为ThreadLocal对象?因为每个线程中可能需要有多个threadLocal变量,也就是ThreadLocalMap里面可能会有多个Entry。

上面我们提到,每个线程第一次调用ThreadLocal.get方法时,内部会走到setInitialValue方法返回一个初始value,其实这个默认初始value为null,这里要注意的一个是,null赋给基本数据类型时会抛空指针。

ThreadLocal的内存泄露问题

我们知道一个线程使用完之后并不会销毁,而是会回到线程池进行复用,也就是说,你保存在当前线程中的变量实例还是绑定在线程上的。

由于WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。如果在某些时候ThreadLocal对象被赋Null的话,弱引用会被GC收集,这样就会导致Entry的Value对象找不到,线程被复用后如果有调用ThreadLocal.get方法的话,方法里面会去做遍历清除Key为null的Entry,但如果一直没调用ThreadLocal.get方法的话就会导致内存泄漏了。

所以一般线程用完ThreadLocal后,要调用threadLocal.remove()方法清除保存在当前线程中的数据变量。

说了这么多好像没讲到怎么用啊,想了解项目中怎么用的,这里这里

最后再补充一段很有意思的代码

就是我们上面提到的get()里map.getEntry(this)的逻辑,其实这里很有讲究,直接附上源码,其中注释都补上了,大家自行享用:

/**

* itself handles only the fast path: a direct hit of existing

* key. It otherwise relays to getEntryAfterMiss. This is

* designed to maximize performance for direct hits, in part

* by making this method readily inlinable.

*

* @param key the thread local object

* @return the entry associated with key, or null if no such

*/

private Entry getEntry(ThreadLocal> key) {

int i = key.threadLocalHashCode & (table.length - 1);

Entry e = table[i];

if (e != null && e.get() == key)

// 散列到的位置,刚好就是要查找的对象

// 直接散列到的位置没找到,那么顺着hash表递增(循环)地往下找

return getEntryAfterMiss(key, i, e);

}

getEntryAfterMiss()方法,这里就有我们上面内存泄漏问题提到的遍历清除Key为null的Entry逻辑

/**

* Version of getEntry method for use when key is not found in

* its direct hash slot.

*

* @param key the thread local object

* @param i the table index for key's hash code

*/

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)

// 如果Key为null,将所有value都设为null,jvm会自动回收掉无用的对象

expungeStaleEntry(i);

else

i = nextIndex(i, len);

e = tab[i];

}

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP