标题是深入理解,其实这个知识点并不难啦,咱一块去研究研究。这是原理篇,实战篇 点这里
ThreadLocal是什么?
线程类Thread有个变量叫做threadLocals
,他的类型就是ThreadLocal.ThreadLocalMap类型
,其实它就是一个map类型,key是当前线程的ThreadLocal对象,值就是你要保存的数据
。ThreadLocal有什么用?
volatile变量
解决多线程间的数据可见性
,也有了锁的同步机制,使变量或代码块在某一时该,只能被一个线程访问,确保数据共享的正确性。其中,Synchronized用于线程间的数据共享的。
另一方面,并不是所有数据都需要共享的,这些不需要共享的数据,让每个线程单独去维护就行了,ThreadLocal就是用于线程间的数据隔离的。
ThreadLocal为变量在每个线程中都创建了一个副本
,每个线程就可以很方便的访问自己内部的副本变量。ThreadLocal类的源码
我们先来看看ThreadLocal类是如何为每个线程创建一个变量的副本的。
/**
* 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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
第一句是取得当前线程
,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocal.ThreadLocalMap。
然后接着下面获取到Entry键值对,注意这里获取Entry时参数传进去的是this,即ThreadLocal实例
,而不是当前线程t。如果获取成功,则返回value值。
如果map为空,则调用setInitialValue方法返回一个初始value,其实这个默认初始value为null。
/* 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.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals
,类型为ThreadLocal.ThreadLocalMap。就是上面提到的每一个线程都自带一个ThreadLocalMap成员变量。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* 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<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap是ThreadLocal的一个静态内部类,ThreadLocalMap的Entry继承了WeakReference,用来实现弱引用,WeakReference不了解的可以点这里,并且使用ThreadLocal作为key值。也就是说WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。
总结一下
每个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals
,这个ThreadLocalMap成员变量的Entry的Key为,当前ThreadLocal变量的WeakReference封装,value为要存储的变量。每个线程中可能需要有多个threadLocal变量
,也就是ThreadLocalMap里面可能会有多个Entry。默认初始value为null
,这里要注意的一个是,null赋给基本数据类型时会抛空指针。
ThreadLocal的内存泄露问题
变量实例还是绑定在线程上的
。ThreadLocal对象被赋Null的话,弱引用会被GC收集
,这样就会导致Entry的Value对象找不到,线程被复用后如果有调用ThreadLocal.get方法的话,方法里面会去做遍历清除Key为null的Entry,但如果一直没调用ThreadLocal.get方法的话就会导致内存泄漏
了。threadLocal.remove()
方法清除保存在当前线程中的数据变量。说了这么多好像没讲到怎么用啊,想了解项目中怎么用的,这里这里
最后再补充一段很有意思的代码
就是我们上面提到的get()里map.getEntry(this)的逻辑,其实这里很有讲究,直接附上源码,其中注释都补上了,大家自行享用:
/**
* Get the entry associated with key. This method
* 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)
// 散列到的位置,刚好就是要查找的对象
return e;
else
// 直接散列到的位置没找到,那么顺着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
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
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];
}
return null;
}