1. ThreadLocal的作用
我们看源码里ThreadLocal类的部分注释:
* 这个类提供线程本地变量,允许每个线程独立拷贝自己的变量,可以通过get和set来获取和设置这些变量。
* 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).
* </pre>
* <p>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).
* 只要线程是存活的,并且ThreadLocal实例是可访问的,那么每个线程就会持有一份对其本地线程副本的隐式引用变量。
* 当线程结束的时候,这个线程拷贝的线程本地实例将会被垃圾回收掉(除非存在其他引用指向这些数据)。
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {}
也就是在线程存活期间,允许每个线程拥有线程本地变量,保证各个线程各自的ThreadLocal变量各自独立,其他线程访问不到。
2. ThreadLocal里放的是什么?
我们来看几段源码: 1)Thread类里有2个变量,threadLocals和inheritableThreadLocals,两个变量都是ThreadLocalMap类型的变量,我们设置的当前线程本地变量其实就是放在这两个map里的。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
```
```
2)ThreadLocal的set方法: 这个方法的作用是给Thread类的threadLocals变量赋值,设置的key是当前ThreadLocal本身,value是任何类型的值(比如们可以是一个Preson类型的值)
public void set(T value) {
//1.获取当前线程
Thread t = Thread.currentThread();
//2.获取当前线程里的threadLocals变量,这个变量式ThreadLocalMap类型的变量,本质上是一个Map
ThreadLocalMap map = getMap(t);
//3.向map中设置key-value,key是当前对象(就是当前ThreadLocal本身),value可以是调用这个set()方法时传入的任何类型时值
if (map != null)
map.set(this, value);
else
//createMap方法是new了一个ThreadLocalMap并且赋值给线程t的threadLocals变量
createMap(t, value);
}
这里面用到的两个方法:getMap(t)用来获取当前线程t的threadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
createMap()方法是创建了一个新的ThreadLocalMap,并赋值给当前线程t的threadLocals变量
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
总结:ThreadLocal存放线程本地变量,其实是将线程本地变量放在了ThreadLocalMap类型的一个Map里,key是当前ThreadLocal本身,value可以是任何类型的值。
3. 通过get()方法获取当前线程的本地变量
public T get() {
//1.获取当前线程
Thread t = Thread.currentThread();
//2.获取当前线程的threadLocals
ThreadLocalMap map = getMap(t);
//3.如果当前线程的threadLocals为非空,从map中获取对应的value值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//3.如果当前线程的threadLocals为空,就设置并返回初始值,初始值为空
return setInitialValue();
}
private T setInitialValue() {
//这个方法返回一个null,所以在没有使用ste方法设置值的情况下,获取到的是一个null值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
4. ThreadLocalMap
现在我们知道,ThreadLocal保存线程本地变量,实际是将数据保存在ThreadLocalMap里的,那么ThreadLocalMap的结构是什么呢? ThreadLocalMap实际上是一个Map,我们看ThreadLocalMap的源码,里面定义了一个继承自弱引用的Entry,那么我们调用ThreadLocal的set方法的时候,其实就是将数据封装成了一个这样的Entry。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
4.1 ThreadLocalMap里的Entry为什么继承自弱引用 WeakReference
Java有4种引用:强、软、弱、虚。这里使用弱引用,是因为弱引用遇到GC就会被回收掉。我们分析一下: 假设我们创建了一个ThreadLocal tl = new ThreadLocal();那么这个th就是个强引用(普通的引用都是强引用),所以,此时有一个强引用tl指向我们新创建的ThreadLocal对象;然后ThreadLocal里还有一个Map叫做ThreadLocalMap,因为我们使用ThreadLocal其实就是将线程本地数据放入ThreadLocalMap里面,Map里其实是一个Entry,Entry里面的key就是ThreadLocal本身,value是我们放的数据,所以除了tl这个引用指向我们的ThreadLocal对象以外,还有一个Entry的key也指向我们的ThreadLocal对象。
所以,当我们的tl引用断开以后,其实还有一个key指向我们的ThreadLocal对象,由于这个key是个弱引用,所以当指向ThreadLocal对象的tl引用断开之后,那么我们的ThreadLocal对象就只有一个弱引用的key指向它,但是弱引用只要遇到GC就会被垃圾回收,所以这时不会产生内存泄漏。
4.2 ThreadLocal导致内存泄漏又是怎么产生的呢?
假设当指向我们的ThreadLocal对象的tl引用被回收了,key的指向也被回收了,如果这时候key指向了一个null值,但是threadLocals的Map是永远存在的,也就是说,如果key是指向null了,那么此时value这个对象还能被访问到吗?访问不到了,所以这个Map里的值会越来越多,还是会内存泄漏。
更多精彩内容请关注公众号:走在Java编程的路上