我对 ThreadLocal 的使用
在我的 Java 类中,我有时ThreadLocal
主要使用 a 来避免不必要的对象创建:
@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 环境就是一个很好的例子。
事实上,当 webapp 停止时,Tomcat 7 会这样抱怨:
严重:Web 应用程序 [] 创建了一个 ThreadLocal,其键类型为 [org.apache.xmlbeans.impl.store.CharUtil$1](值 [org.apache.xmlbeans.impl.store.CharUtil$1@2aace7a7])和一个值[java.lang.ref.SoftReference] 类型(值 [java.lang.ref.SoftReference@3d9c9ad4])但在 Web 应用程序停止时未能将其删除。线程将随着时间的推移而更新,以尝试避免可能的内存泄漏。2012 年 12 月 13 日 12:54:30 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
(在那种特殊情况下,甚至我的代码都没有这样做)。
谁该受责备?
这似乎不太公平。Tomcat 指责我(或我班的用户)做了正确的事情。
归根结底,这是因为 Tomcat 想将它提供给我的线程重用于其他Web 应用程序。(呃 - 我觉得很脏。)可能,这对 Tomcat 来说不是一个很好的策略 - 因为线程实际上确实有/导致状态 - 不要在应用程序之间共享它们。
然而,这个政策至少是普遍的,即使它是不可取的。我觉得我有义务 - 作为ThreadLocal
用户,为我的班级提供一种方式来“释放”我的班级附加到各种线程的资源。
但是该怎么办呢?
在这里做什么是正确的?
在我看来,servlet 引擎的线程重用策略似乎与ThreadLocal
.
但也许我应该提供一个工具,让用户说“开始,与这个类相关的邪恶线程特定状态,即使我无法让线程死亡并让 GC 做它的事情?”。我有可能做到这一点吗?我的意思是,我不能安排在过去某个时间ThreadLocal#remove()
看到的每个线程上被调用。ThreadLocal#initialValue()
还是有其他方法?
还是我应该对我的用户说“去给自己找一个像样的类加载器和线程池实现”?
EDIT#1:阐明了如何threadCal
在不知道线程生命周期的 vanailla 实用程序类中使用
EDIT#2:修复了线程安全问题DateSensitiveThing