21

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

4

7 回答 7

17

因为ThreadLocalMapcurrentThreadvalue现在如果你不删除正在使用它的线程中的值,那么它将造成内存泄漏。

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

从源代码ThreadLocal

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

于 2012-09-14T12:49:05.290 回答
15

set总是替换旧值。

这是真的

  • Calendar.set() 和 Date.set()
  • 位集.set()
  • 列表.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 级别移动了很多,但最小大小仍然很小。

于 2012-09-14T12:47:44.760 回答
5

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

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

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

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

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

要注意与线程池的一致性

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

于 2018-09-17T14:16:43.240 回答
4

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

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

于 2012-09-14T12:48:41.870 回答
3

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

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

于 2012-09-14T12:50:23.600 回答
1

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

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

于 2012-09-14T13:16:39.393 回答
0

如果线程完成,threadLocalMap则将使用线程完成。您不需要删除它。但是如果线程被用于回收,则需要删除threadLocalMap.

于 2019-01-23T09:57:35.593 回答