【问题标题】:Threadlocal remove?线程本地删除?
【发布时间】:2012-09-07 15:18:02
【问题描述】:

当我使用ThreadLocal 时,我应该总是在我完成后调用remove() 还是当我使用set 时,旧值无论如何都会被替换,所以remove 是多余的?

【问题讨论】:

    标签: java multithreading thread-local


    【解决方案1】:

    set 总是替换旧值。

    这是真的

    • Calendar.set() 和 Date.set()
    • BitSet.set()
    • List.set()
    • 二传手

    你的意思是不删除它就不会被 GCed?

    在线程死亡之前它不会被删除。没有你调用 remove() 它不会消失在你身上

    这是否是内存泄漏取决于您的程序。您必须使用大型线程本地对象创建大量线程,出于某种原因您不需要这些对象。例如具有 1 KB 对象的 1000 个线程最多可能会浪费 1 MB,但如果您正在执行此类操作,这表明存在设计问题。


    唯一可能发生内存泄漏的地方是。

    for (int i = 0; ; i++) {
        // don't subclass Thread.
        new Thread() {
            // this is somewhat pointless as you are defining a ThreadLocal per thread.
            final ThreadLocal<Object> tlObject = new ThreadLocal<Object>() {
            };
    
            public void run() {
                tlObject.set(new byte[8 * 1024 * 1024]);
            }
        }.start();
        Thread.sleep(1);
        if (i % 1000 == 0) {
            System.gc();
            System.out.println(i);
        }
    }
    

    -verbosegc 打印。

    [Full GC 213548K->49484K(3832192K), 0.0334194 secs]
    39000
    [GC 2786060K->82412K(3836864K), 0.0132035 secs]
    [GC 2815569K->107052K(3836544K), 0.0212252 secs]
    [GC 2836162K->131628K(3837824K), 0.0199268 secs]
    [GC 2867613K->156204K(3837568K), 0.0209828 secs]
    [GC 2886894K->180780K(3838272K), 0.0191244 secs]
    [GC 2911942K->205356K(3838080K), 0.0187482 secs]
    [GC 421535K->229932K(3838208K), 0.0192605 secs]
    [Full GC 229932K->49484K(3838208K), 0.0344509 secs]
    40000
    

    注意:full GC后的大小是一样的49484K

    在上述情况下,您将拥有一个 ThreadLocal,它指的是 Thread,它指的是 ThreadLocal。但是,由于线程已死,它不会导致内存泄漏,因为它成为关注对象,即当 A -> B 和 B -> A

    我在循环中运行了上面的示例几分钟,GC 级别移动了很多,但最小大小仍然很小。

    【讨论】:

    • @Jim 好问题,已添加答案。
    • Peter,在扩展的 ThreadLocals 上可能会发生泄漏,因为线程将保留对的引用。考虑取消部署和动态类加载器。
    • ThreadLocal 实际上并不持有对象。该对象保存在 Thread 本身的 ThreadLocalMap 中,其中 ThreadLocal 对象是键。当线程停止时,它的所有线程本地对象都可能变得无法访问(除非它们被保存在其他地方)
    • ThreadLocal 不是final,但我唯一一次看到它的子类是定义一个initialValue。如果这是在 Thread 的上下文中定义的,那么您就有问题了,但是子类化 Thread 通常被认为是一个坏主意。
    • ThreadLocalMap 中的条目扩展了 WeakReference 并且该值在 ThreadLocalMap.Entry[] 中有一个 HARD 引用——因为该值将引用类加载器和类加载器到 ThreadLocal --> #1 泄密者。不过,您需要动态类加载来充分利用它。
    【解决方案2】:

    set:将此线程局部变量的当前线程副本设置为指定值。

    意味着该内存位置中的任何内容现在都将被您通过 set 传递的内容覆盖

    【讨论】:

      【解决方案3】:

      因为ThreadLocal 有Map 的currentThread 和value,现在如果你不删除正在使用它的线程中的值,那么它会造成内存泄漏。

      您应该始终调用 remove,因为 ThreadLocal 类将值来自 ThreadLocal.Values localValues 定义的 Thread 类; 这也会导致持有 Thread 和关联对象的引用。

      来自ThreadLocal的源码

      该值将设置为 null,并且基础条目仍将存在。

      【讨论】:

      • 你的意思是没有remove它不会被GCed?
      • 这与价值无关,它与Thread 相关,如果你不调用 remove 它在ThreadLocal 中有引用,它将永远不会被 GCed。
      • 这个答案现在已经过时了。 ThreadLocal 的当前实现具有每个线程的值映射。来自 JavaDoc:“在线程消失后,它的所有线程本地实例副本都将受到垃圾收集”。
      【解决方案4】:

      如果您尝试remove 的变量在线程的下一次执行中将始终为set,我不会担心删除它。 set 将覆盖其值。

      但是,如果您仅在某些情况下设置该变量(例如,仅在处理特定类型的请求时),删除它可能会很方便,这样它就不会在例如线程处于放回池中。

      【讨论】:

        【解决方案5】:

        我会让它变得简单: 如果出于任何原因扩展 ThreadLocal,请使用 remove()。在香草 ThreadLocal 上使用 set(null)。 基本上不在 extended ThreadLocal 上使用 ThreadLocal.remove() 会导致内存泄漏(最有可能是 ClassLoader)

        如果您需要更多详细信息,请发表评论。

        【讨论】:

        • 我不明白你对set(null) 和remove 的区别。我应该在调用set 之前先调用set(null) 吗?为什么会导致泄漏?
        • @bestsss 有趣的区别。
        • @Jim,不通过set(null) 或remove() 删除对象,线程继续运行将是导致泄漏的原因。绝对没有必要在 set(xxx) 之前调用 set。
        • @irreputable,是的,我从不扩展 ThreadLocals 并在其中保留非引导/系统类加载器加载的类,除非在 try/finally{remove} 中。链接到my guidelines 以避免泄漏。
        • @bestsss 最近 jdk 的 ThreadLocalMap 引用键很弱,所以应该没问题。您是否担心其他 ThreadLocal 实现?
        【解决方案6】:

        不你不必“总是调用 remove()”而不是 set()

        如果您担心这样做会导致内存泄漏,javadoc 会这样说

        只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程都持有对其线程局部变量副本的隐式引用;线程离开后,它的所有线程本地实例副本都将受到垃圾回收(除非存在对这些副本的其他引用)。

        因此不调用 remove() 不会阻止线程本地实例被正确地垃圾收集,并且不会导致内存泄漏。

        您还可以查看 ThreadLocal 实现,它使用 WeakReferences 来实现这种“隐式引用”机制

        但要注意与线程池的一致性

        仅在线程池中使用 set() 方法,您可能更喜欢 remove() 一个 ThreadLocal 实例,而不是在另一个“工作单元”中使用同一个线程。 因为您可能希望避免由于某种原因未调用 set 方法,并且您的 ThreadLocal 仍然附加到它不属于的上下文/处理的情况。

        【讨论】:

          【解决方案7】:

          如果线程完成,threadLocalMap 将与线程一起完成。您不需要删除它。但是如果线程被回收使用则需要去掉threadLocalMap的值。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-29
            • 1970-01-01
            • 2016-07-08
            • 2013-05-20
            • 2013-05-27
            相关资源
            最近更新 更多