ThreadLocal注意事项

本文深入探讨了线程池中的ThreadLocal对象的内存管理问题及其生命周期特性,包括ThreadLocal变量传递、内存泄露分析以及解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       在使用了线程池(如Executor)的情况下,那么即使父线程已经结束,子线程依然存在并被池化。这样,线程池中的线程在下一次请求被执行的时候,ThreadLocal对象的get()方法返回的将不是当前线程中设定的变量,因为池中的“子线程”根本不是当前线程创建的,当前线程设定的 ThreadLocal变量也就无法传递给线程池中的线程。因此,必须将外部线程中的ThreadLocal变量显式地传递给线程池中的线程。



关于ThreadLocal的内存泄露问题:
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.");
        }
    }
}
运行结果:
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的强引用链条才会断掉:

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.");
        }
    }
}
运行结果:
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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值