ThreadLocal在面试经常会被问到,这里来做一个透彻的解析
介绍
ThreadLocal我们叫他线程变量,为什么叫线程变量,主要是因为他生命周期是一个线程内。
由于一个线程可以存在多个ThreadLocal线程变量,所以Thread中有一个ThreadLocalMap的属性专门用于存储多个线程变量,map的key正是用ThreadLocal,map的value才是存的真正ThreadLocal.get()的值
源码分析
ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取线程中的存储ThreadLocal的Map
if (map != null)
map.set(this, value); //如果map不为空,则调用set直接存储值,ThreadLocal.this做为key,ThreadLocal的实际值做为value
else
createMap(t, value); //创建一个map
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal.remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread()); //获取当前线程存储线程变量的map
if (m != null)
m.remove(this); //调用map的remove方法删除ThreadLocal及其存储的值
}
ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取线程中的存储线程变量的map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
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;
}
protected T initialValue() { //默认初始化方法返回空
return null;
}
静态方法ThreadLocal.withInitial()
jdk1.8新加入的方法,创建一个ThreadLocal的子类SuppliedThreadLocal,该类实现了ThreadLocal的initialValue初始化方法,使用Supplier来生成一个值
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier); //创建一个ThreadLocal的子类
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) { //构造方法传入一个Supplier,用于生成值
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() { //重写initialValue方法,使用Supplier生成值
return supplier.get();
}
}
ThreadLocal造成内存泄漏
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
从类图及代码中可以看到,ThreadLocal.Entry继承了弱引用WeakReference,然而value却是强引用,根据弱引用的特性,当一个变量只有被WeakReference引用时gc将回收变量的空间,这样造成map的key被回收了value却还没被回收,好在ThreadLocal在调用get/set/remove时会触发ThreadLocalMap.expungeStaleEntry回收内存,一定程度上解决了这个问题。
推荐使用方法
当用完ThreadLocal后,记得调用remove()方法,这样就不会造成内存泄漏
ThreadLocal内存泄漏案例
/**
* 测试内存泄漏
* 根源是没有调用ThreadLocal.remove方法
*/
@Test
public void memoryLeak() throws InterruptedException {
ThreadLocal<MyObject> threadLocal = new ThreadLocal() {
@Override
protected void finalize() throws Throwable {
System.out.println("ThreadLocal被gc");
}
};
threadLocal.set(new MyObject("1"));
threadLocal.set(new MyObject("2")); //如果不调用remove,最后一个set的value不会被回收
// threadLocal.remove(); //不调用会内存泄漏,MyObject被ThreadLocalMap.Entry的value一直强引用着
threadLocal = null;
System.gc(); //threadLocal和new MyObject("1")被gc回收,new MyObject("2")还没被回收
TimeUnit.SECONDS.sleep(1);
//通过其他ThreadLocal的get/set/remove方法触发清除ThreadLocalMap.Entry中new MyObject("2")的引用
for (int i = 0; i < 11; i++) {
ThreadLocal otherLocal = new ThreadLocal();
otherLocal.set(i);
}
System.gc(); //new MyObject("2")终于被这次gc回收
TimeUnit.SECONDS.sleep(1);
}
通过程序日志我们可以知道:
- 由于没有调用threadLocal.remove()造成new MyObject(“2”)无法被第一次gc回收
- 通过其他ThreadLocal实例的set()方法触发了清除new MyObject(“2”)的强引用,第二次gc回收