1.使用例子:
public class ThreadLocalExample {
public static void main(String[] args) {
//每个线程设置变量,相当于设置到ThreadLocalMap中的Entry中(key是线程名字,value是设置的值)
//但是key是弱引用,value是强引用,当GC的时候会回收key,但是value还在,导致内存泄漏
//下次线程再次创建会创建新的,所以内存泄漏了
ThreadLocal<String> threadLocal = new ThreadLocal<>();
for (int i=0;i<10;i++){
new Thread(()->{
System.out.println("线程名字"+Thread.currentThread().getName());
threadLocal.set(Thread.currentThread().getName()+"**");
System.out.println(threadLocal.get());
System.out.println("-----------");
}).start();
}
//线程名字Thread-8
//Thread-8**
//-----------
//线程名字Thread-9
//Thread-9**
//-----------
}
}
2.原理:ThreadLocal类有内部类ThreadLocalMap,ThreadLocalMap类有内部类Entry(key是线程名字,value是每个线程设的值)
ThreadLocal主要功能:每个线程提供一个专属的局部变量(一个线程的变量对应ThreadLocalMap中自己线程名字的Entry中的value)
因为是专属的,所以是线程安全的
ThreadLocal类的内部类set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal类的内部类ThreadLocalMap:
static class ThreadLocalMap {
//Entry就是存ThreadLocalMap的key和value。
//key是弱引用,gc时不管内存够不够都会回收这个对象,而value是强引用,不会回收。导致内存泄漏
//当同一个线程再次创建专属变量时发现不存在又新建,但是老的value对象没有回收,就出现了内存泄漏。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
内部类ThreadLocalMap的set(this,value)方法:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//key和value赋值给ThreadLocalMap类的内部类Entry:看上面的Entry是继承WeakReference,是弱引用,GC的时候回收key
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
3.应用场景
每个线程设置自己的变量,获取的也是自己的变量
SpringMVC单例的情况下,controller中可以加ThreadLocal对象,用来给不同线程设置自己的专属变量,解决线程安全
4.内存泄漏的原因
ThreadLocalMap类的内部类Entry:是继承WeakReference,是弱引用,GC的时候回收key。但是value没有回收,导致了内存泄漏。
同样的线程下次再次设值得时候发现key没有,然后新建,老的value一直在,所以内存泄漏
ThreadLocal提供了解决方案:
每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。