【发布时间】:2012-09-07 15:18:02
【问题描述】:
当我使用ThreadLocal 时,我应该总是在我完成后调用remove() 还是当我使用set 时,旧值无论如何都会被替换,所以remove 是多余的?
【问题讨论】:
标签: java multithreading thread-local
当我使用ThreadLocal 时,我应该总是在我完成后调用remove() 还是当我使用set 时,旧值无论如何都会被替换,所以remove 是多余的?
【问题讨论】:
标签: java multithreading thread-local
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 级别移动了很多,但最小大小仍然很小。
【讨论】:
final,但我唯一一次看到它的子类是定义一个initialValue。如果这是在 Thread 的上下文中定义的,那么您就有问题了,但是子类化 Thread 通常被认为是一个坏主意。
set:将此线程局部变量的当前线程副本设置为指定值。
意味着该内存位置中的任何内容现在都将被您通过 set 传递的内容覆盖
【讨论】:
因为ThreadLocal 有Map 的currentThread 和value,现在如果你不删除正在使用它的线程中的值,那么它会造成内存泄漏。
您应该始终调用 remove,因为 ThreadLocal 类将值来自 ThreadLocal.Values localValues 定义的 Thread 类; 这也会导致持有 Thread 和关联对象的引用。
来自ThreadLocal的源码
该值将设置为 null,并且基础条目仍将存在。
【讨论】:
remove它不会被GCed?
Thread 相关,如果你不调用 remove 它在ThreadLocal 中有引用,它将永远不会被 GCed。
如果您尝试remove 的变量在线程的下一次执行中将始终为set,我不会担心删除它。 set 将覆盖其值。
但是,如果您仅在某些情况下设置该变量(例如,仅在处理特定类型的请求时),删除它可能会很方便,这样它就不会在例如线程处于放回池中。
【讨论】:
我会让它变得简单:
如果出于任何原因扩展 ThreadLocal,请使用 remove()。在香草 ThreadLocal 上使用 set(null)。
基本上不在 extended ThreadLocal 上使用 ThreadLocal.remove() 会导致内存泄漏(最有可能是 ClassLoader)
如果您需要更多详细信息,请发表评论。
【讨论】:
set(null) 和remove 的区别。我应该在调用set 之前先调用set(null) 吗?为什么会导致泄漏?
set(null) 或remove() 删除对象,线程继续运行将是导致泄漏的原因。绝对没有必要在 set(xxx) 之前调用 set。
不你不必“总是调用 remove()”而不是 set()
如果您担心这样做会导致内存泄漏,javadoc 会这样说
只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程都持有对其线程局部变量副本的隐式引用;线程离开后,它的所有线程本地实例副本都将受到垃圾回收(除非存在对这些副本的其他引用)。
因此不调用 remove() 不会阻止线程本地实例被正确地垃圾收集,并且不会导致内存泄漏。
您还可以查看 ThreadLocal 实现,它使用 WeakReferences 来实现这种“隐式引用”机制
但要注意与线程池的一致性
仅在线程池中使用 set() 方法,您可能更喜欢 remove() 一个 ThreadLocal 实例,而不是在另一个“工作单元”中使用同一个线程。 因为您可能希望避免由于某种原因未调用 set 方法,并且您的 ThreadLocal 仍然附加到它不属于的上下文/处理的情况。
【讨论】:
如果线程完成,threadLocalMap 将与线程一起完成。您不需要删除它。但是如果线程被回收使用则需要去掉threadLocalMap的值。
【讨论】: