我的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
的用户,我觉得我有义务提供一种方法,让我的类“释放”它附加到各个线程上的资源。
但怎么做呢?
在这里应该做什么才是正确的呢?
对我来说,Servlet 引擎的线程重用策略与 ThreadLocal
的意图相抵触。
但也许我应该提供一种方法,允许用户说“离开这个类与线程相关的邪恶状态,即使我没有办法让线程死亡并让 GC 做它的事情?”。我能做到吗?我的意思是,我不能像调用 ThreadLocal#remove()
那样安排在过去某个时刻看到 ThreadLocal#initialValue()
的每个线程被调用。或者还有其他方法吗?
或者我应该告诉我的用户“去找一个好的类加载器和线程池实现”?
EDIT#1: 在一个不知道线程生命周期的普通实用程序类中澄清了如何使用 threadCal