在使用了线程池(如Executor)的情况下,那么即使父线程已经结束,子线程依然存在并被池化。这样,线程池中的线程在下一次请求被执行的时候,ThreadLocal对象的get()方法返回的将不是当前线程中设定的变量,因为池中的“子线程”根本不是当前线程创建的,当前线程设定的 ThreadLocal变量也就无法传递给线程池中的线程。因此,必须将外部线程中的ThreadLocal变量显式地传递给线程池中的线程。
关于ThreadLocal的内存泄露问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class ThreadLocalTest { public static void main(String[] args) throws InterruptedException { ThreadLocal tl = new MyThreadLocal(); tl.set(new My50MB());
tl = null; System.out.println("Full GC"); System.gc(); TimeUnit.SECONDS.sleep(1); }
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."); } } }
|
运行结果:
1 2
| Full GC My threadlocal 1 MB finalized.
|
TimeUnit.SECONDS.sleep(1)是为了给GC一个反应的时间. GC优先级低,即使调用了system.gc也不能立刻执行.所以sleep 1秒.
于是有了如下分析:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块 value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法. 说的也比较正确,当value不再使用的时候,调用remove的确是很好的做法.但内存泄露一说却不正确。
在threadlocal的生命周期中,存在的引用如下图: 实线代表强引用,虚线代表弱引用:

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal.当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以 threadlocal将会被gc回收. 但是,value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
弱引用只存在于key上,所以key会被回收. 而value还存在着强引用.只有thead退出以后,value的强引用链条才会断掉. 改进如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.zero.threadlocal;
public class ThreadLocalTest { 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; System.out.println("Full GC"); System.gc(); }
}).start();
System.gc(); TimeUnit.SECONDS.sleep(1); System.gc(); TimeUnit.SECONDS.sleep(1); System.gc(); TimeUnit.SECONDS.sleep(1); }
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."); } } }
|
运行结果:
1 2 3
| Full GC My threadlocal 1 MB finalized. My 50 MB finalized.
|
可以看到,所有的都回收了.为什么要多次调用system.gc()? 这和finalize方法的策略有关系. finalize是一个特别低优先级的线程,当执行gc时,如果一个对象需要被回收,先执行它的finalize方法.这意味着,本次gc可能无法真正回收这个具有finalize方法的对象.留待下次回收. 这里多次调用system.gc正是为了给finalize留些时间.
从上面的例子可以看出,当线程退出以后,我们的value被回收了. 这是正确的.这说明内存并没有泄露.栈中还存在着对value的强引用路线。 然而在使用线程池时,线程一般是不会被销毁的。
参考:http://my.oschina.net/xpbug/blog/113444