gpt4 book ai didi

java - 当类暴露给线程池时,清理 ThreadLocal 资源真的是我的工作吗?

转载 作者:IT老高 更新时间:2023-10-28 20:42:55 28 4
gpt4 key购买 nike

我对 ThreadLocal 的使用

在我的 Java 类中,我有时会使用 ThreadLocal主要是为了避免不必要的对象创建:

@net.jcip.annotations.ThreadSafe
public class DateSensitiveThing {

private final Date then;

public DateSensitiveThing(Date then) {
this.then = then;
}

private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>() {
@Override
protected Calendar initialValue() {
return new GregorianCalendar();
}
};

public Date doCalc(int n) {
Calendar c = threadCal.get();
c.setTime(this.then):
// use n to mutate c
return c.getTime();
}
}

我这样做是有正当理由的 - GregorianCalendar是那些有状态的、可变的、非线程安全的对象之一,它提供跨多个调用的服务,而不是表示一个值。此外,实例化被认为是“昂贵的”(这是否属实不是这个问题的重点)。 (总的来说,我真的很佩服它:-))

Tomcat 如何提示

但是,如果我在任何汇集线程的环境中使用这样的类 - 并且我的应用程序无法控制这些线程的生命周期 - 那么就有可能发生内存泄漏。 Servlet 环境就是一个很好的例子。

事实上,当 web 应用程序停止时,Tomcat 7 会发出这样的提示:

SEVERE: The web application [] created a ThreadLocal with key of type [org.apache.xmlbeans.impl.store.CharUtil$1] (value [org.apache.xmlbeans.impl.store.CharUtil$1@2aace7a7]) and a value of type [java.lang.ref.SoftReference] (value [java.lang.ref.SoftReference@3d9c9ad4]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak. Dec 13, 2012 12:54:30 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks



(在这种特殊情况下,甚至我的代码都没有这样做)。

谁是罪魁祸首?

这似乎不太公平。 Tomcat 责备我(或我类的用户)做正确的事。

归根结底,这是因为 Tomcat 想要将它提供给我的线程重用于其他 Web 应用程序。 (呃 - 我觉得很脏。)对于 Tomcat 来说,这可能不是一个很好的策略 - 因为线程实际上确实有/导致状态 - 不要在应用程序之间共享它们。

然而,这个政策至少是普遍的,即使它是不可取的。我觉得我有义务 - 作为 ThreadLocal用户,为我的类(class)提供一种“释放”我类(class)附加到各种线程的资源的方法。

但是该怎么办呢?

在这里做什么是正确的?

在我看来,servlet 引擎的线程重用策略似乎与 ThreadLocal 背后的意图不一致。 .

但也许我应该提供一个工具来允许用户说“消失了,与这个类相关的邪恶线程特定状态,即使我无法让线程死亡并让 GC 做它的事情?”。我什至有可能这样做吗?我的意思是,我不能安排 ThreadLocal#remove()在看到 ThreadLocal#initialValue() 的每个线程上调用在过去的某个时间。或者还有其他方法吗?

或者我应该对我的用户说“去给自己找一个像样的类加载器和线程池实现”?

编辑#1:阐明了如何 threadCal用于不知道线程生命周期的 vanilla 实用程序类
编辑#2:修复了 DateSensitiveThing 中的线程安全问题

最佳答案

咳咳,这是旧闻

嗯,这个派对有点晚了。 2007 年 10 月,Josh Bloch(java.lang.ThreadLocal 与 Doug Lea 的合著者)wrote :

"The use of thread pools demands extreme care. Sloppy use of thread pools in combination with sloppy use of thread locals can cause unintended object retention, as has been noted in many places."



那时人们还在提示 ThreadLocal 与线程池的不良交互。但乔希确实批准了:

“性能的每线程实例。Aaron 的 SimpleDateFormat 示例(上图)就是这种模式的一个示例。”

一些教训
  • 如果您将任何类型的对象放入任何对象池,您必须提供一种“稍后”删除它们的方法。
  • 如果你使用 ThreadLocal 'pool' ,您可以这样做的选择有限。任何一个:
    a) 你知道 Thread (s) 当您的应用程序完成时,您放置值的位置将终止;或者
    b) 您可以稍后安排调用 ThreadLocal#set() 的同一线程在应用程序终止时调用 ThreadLocal#remove()
  • 因此,您使用 ThreadLocal 作为对象池将在您的应用程序和类的设计上付出沉重的代价。好处不是免费的。
  • 因此,使用 ThreadLocal 可能是一种过早的优化,尽管 Joshua Bloch 在“Effective Java”中敦促您考虑它。

  • 简而言之,决定使用 ThreadLocal 作为对“每个线程实例池”的快速、无竞争访问的一种形式并不是一个轻率的决定。

    注意:除“对象池”之外,ThreadLocal 还有其他用途,并且这些类(class)不适用于 ThreadLocal 仅打算临时设置的情况,或者有真正的每线程状态要保持的情况踪迹。

    库实现者的后果

    对库实现者有一些后果(即使这些库是您项目中的简单实用程序类)。

    任何一个:
  • 您使用 ThreadLocal,充分意识到您可能会用额外的负担“污染”长时间运行的线程。如果您正在实现 java.util.concurrent.ThreadLocalRandom ,可能是合适的。 (如果您没有在 java.* 中实现,Tomcat 可能仍然会提示您图书馆的用户)。有趣的是要注意 java.* 的纪律。很少使用 ThreadLocal 技术。

  • 或者
  • 您使用 ThreadLocal,并为您的类/包提供客户端:
    a) 有机会选择放弃优化(“不要使用 ThreadLocal ......我无法安排清理”);和
    b) 一种清理 ThreadLocal 资源的方法(“可以使用 ThreadLocal ......我可以安排所有使用你调用的线程 LibClass.releaseThreadLocalsForThread() 当我完成它们。

  • 但是,使您的库“难以正确使用”。

    或者
  • 您让您的客户有机会提供他们自己的对象池实现(可能使用 ThreadLocal 或某种同步)。 (“好吧,我可以给你一个 new ExpensiveObjectFactory<T>() { public T get() {...} },如果你觉得真的有必要的话”。

  • 没那么糟糕。如果对象真的那么重要并且创建起来那么昂贵,那么显式池化可能是值得的。

    或者
  • 你决定它对你的应用程序来说没有那么多值(value),并找到一种不同的方法来解决这个问题。那些创建成本高昂的、可变的、非线程安全的对象让你很痛苦……无论如何,使用它们真的是最好的选择吗?

  • 备择方案
  • 常规对象池,以及所有竞争同步。
  • 不池对象 - 只需在本地范围内实例化它们并稍后丢弃。
  • 不池化线程(除非你可以在你喜欢的时候安排清理代码)——不要在 JaveEE 容器中使用你的东西
  • 线程池足够聪明,可以清理 ThreadLocals 而不会对你发牢骚。
  • 线程池在“每个应用程序”的基础上分配线程,然后在应用程序停止时让它们死亡。
  • 线程池容器和应用程序之间的协议(protocol),它允许注册“应用程序关闭处理程序”,容器可以安排在用于服务应用程序的线程上运行......下一个可用。例如。 servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})

  • 很高兴在 future 的 JavaEE 规范中看到关于最后 3 项的一些标准化。

    引导笔记

    实际上,一个 GregorianCalendar 的实例化很轻。这是对 setTime()的不可避免的调用这会导致大部分工作。它也不会在线程执行的不同点之间保持任何重要状态。放一个 CalendarThreadLocal不太可能给你超过你付出的代价......除非分析确实显示了 new GregorianCalendar() 中的热点。 .
    new SimpleDateFormat(String)相比之下,它很昂贵,因为它必须解析格式字符串。解析后,对象的“状态”对于同一线程以后的使用很重要。这是一个更好的选择。但是实例化一个新的可能仍然比给你的类额外的责任“更便宜”。

    关于java - 当类暴露给线程池时,清理 ThreadLocal 资源真的是我的工作吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13852632/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com