ThreadLocal原理
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
存储结构
public class Thread implements Runnable {
......
ThreadLocal.ThreadLocalMap threadLocals = null;//一个线程对应一个ThreadLocalMap
......
}
public class ThreadLocal<T> {
......
static class ThreadLocalMap {//静态内部类
static class Entry extends WeakReference<ThreadLocal> {//键值对
//Entry是ThreadLocal对象的弱引用,this作为键(key)
/** The value associated with this ThreadLocal. */
Object value;//ThreadLocal关联的对象,作为值(value),也就是所谓的线程本地变量
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
......
private Entry[] table;//用数组保存所有Entry,采用线性探测避免冲突
}
......
}
ThreadLocal主要方法
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
内存泄漏问题
对于ThreadLocalMap 的键值对Entry,key为ThreadLocal实例,value为线程本地变量。而Entry继承自WeakReference<ThreadLocal>。WeakReference为弱引用,也就是说Key是一个弱引用(引用ThreadLocal实例)。
为什么使用弱引用
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
下面看一段示例:
public class Test {
public static class MyThreadLocal extends ThreadLocal {
private byte[] a = new byte[1024*1024*1];
@Override
public void finalize() {
System.out.println("My threadlocal 1 MB finalized.");
}
}
public static class My50MB {//占用内存的大对象
private byte[] a = new byte[1024*1024*50];
@Override
public void finalize() {
System.out.println("My 50 MB finalized.");
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocal tl = new MyThreadLocal();
tl.set(new My50MB());
tl=null;//断开ThreadLocal的强引用
System.out.println("Full GC 1");
System.gc();
}
}).start();
System.out.println("Full GC 2");
System.gc();
Thread.sleep(1000);
System.out.println("Full GC 3");
System.gc();
Thread.sleep(1000);
System.out.println("Full GC 4");
System.gc();
Thread.sleep(1000);
}
}
输出:
Full GC 2
Full GC 1
My threadlocal 1 MB finalized.
Full GC 3
My 50 MB finalized.
Full GC 4
可以看出,当key置为null时,threadLocal的强引用断开,key的内存就可以在下一次GC之后释放。而value的值要等到线程销毁时才释放。
当遇到线程不会被回收的情况时,比如使用线程池,就可能会发生内存泄漏。
解决方法
每次执行完毕后,要使用remove()方法来清空对象。