ThreadLocal,我们依然从一个例子看起。
例子public class ThreadLocalTest2 {
public static void main(String[] args) {
// ①,强引用tl1
ThreadLocal tl1 = new ThreadLocal();
tl1.set(11);
System.gc();
tl1.get();
System.gc();
// tl1.remove();
// ②,没有强引用
new ThreadLocal<>().set(22);
System.gc();
// ③,方法里的tl
ThreadLocalTest2 test2 = new ThreadLocalTest2();
test2.test1();
System.gc();
Thread thread = Thread.currentThread();
System.out.println(thread);
}
void test1() {
ThreadLocal tl = new ThreadLocal<>();
tl.set(33);
// tl.remove();
}
}
debug模式下,我们看下thread里ThreadLocalMap里的情况,如下图。
①因为存在强引用,所以对应的referent不为null。
②没有强引用,垃圾回收后,referent为null。
③ThreadLocal仅在test1()方法中有效,所以垃圾回收后,referent为null。
public class ThreadLocal{ ... static class ThreadLocalMap { static class Entry extends WeakReference > { Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } } ... } ... }
从线程到ThreadLocal.set(value)的value的引用关系就是这样的
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> valueset(T value)
设置线程本地变量的内容。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()
获取线程本地变量的value值
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();
}
- initialValue()
设置 ThreadLocal 的初始化值,未 set(T value) 初次获取 Thread 对应的 Value 值时会调用,即被 setInitialValue 方法调用。需要重写该方法。
protected T initialValue() {
return null;
}
remove()
移除线程本地变量。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocal使用完后,一定要调用remove()!!!
几个问题 ThreadLocalMap的KEY为什么设计成弱引用不手动调用remove()方法,
1.容易发生内存泄漏。
2.线程池场景下后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。
- 如果设计成强引用,即使我们使用完ThreadLocal,只要线程没有结束,线程对应的ThreadLocal就不会被回收。这个时候当ThreadLocal太多的时候就会出现内存泄漏的问题。
- 设计为弱引用后,只要没有了强引用,jvm垃圾回收时,便会将key(ThreadLocal)进行回收。
降低内存泄漏的风险。
这里也用一个例子,来说明value为什么不是弱引用
public class TLLeakTest {
public static void main(String[] args) {
// ①,key和value均为WeakReference
Map, WeakReference> map = new HashMap<>(8);
WeakReference key = new WeakReference<>(1);
WeakReference value = new WeakReference<>(777);
map.put(key,value);
System.gc();
System.out.println("get1: " + map.get(key).get());
// ②,key为WeakReference,value为HardReference
Map, Integer> map2 = new HashMap<>(8);
WeakReference key2 = new WeakReference<>(1);
map2.put(key2, 999);
System.gc();
System.out.println("get2: "+ map2.get(key2));
}
}
get1: null get2: 999
以上结果,可以说明为什么value不能为弱引用。
如果value也为弱引用,那么得到的结果会为null。
使用完ThreadLocal,如果没有及时remove(),那么key因为是弱引用就会被回收,但value是强引用,value没被回收,导致value永远存在,value越来越多后,造成内存泄漏。



