Dav*_*ock 42 java concurrency thread-local threadpool
在我的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();
}
}
Run Code Online (Sandbox Code Playgroud)
我这样做是出于正当的原因 - GregorianCalendar是那些光荣的有状态,可变,非线程安全的对象之一,它提供跨多个调用的服务,而不是表示值.此外,它被认为是"昂贵的"实例化(这是否真实不是这个问题的重点).(总的来说,我真的很佩服它:-))
但是,如果我在任何聚集线程的环境中使用这样的类 - 并且我的应用程序无法控制这些线程的生命周期 - 那么就有可能发生内存泄漏.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 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
(在特定情况下,甚至我的代码都没有这样做).
这似乎不太公平.Tomcat责备我(或我班级的用户)做正确的事.
最终,这是因为Tomcat希望重用它为我提供的线程,以及其他 Web应用程序.(呃 - 我觉得很脏.)可能,这对Tomcat而言并不是一个很好的策略 - 因为线程确实有/导致状态 - 不要在应用程序之间共享它们.
但是,这项政策至少是常见的,即使这是不可取的.我觉得作为一个ThreadLocal用户,我有义务为我的班级提供一种方法来'释放'我班级附加到各种线程的资源.
这里做什么是正确的?
对我来说,似乎servlet引擎的线程重用策略与背后的意图不一致ThreadLocal.
但也许我应该提供一个工具来允许用户说"与这个类关联的恶意,特定于线程的特定状态,即使我无法让线程死掉并让GC做它的事情?".我甚至可以这样做吗?我的意思是,这并不是说我可以安排在过去的某个时间ThreadLocal#remove()看到的每个主题上调用ThreadLocal#initialValue().或者还有另一种方式吗?
或者我应该对我的用户说"去为自己做一个体面的类加载器和线程池实现"?
编辑#1:澄清如何threadCal在不知道线程生命周期的vanailla实用程序类中使用
编辑#2:修复了线程安全问题DateSensitiveThing
Dav*_*ock 31
嗯,这个派对有点晚了.2007年10月,Josh Bloch(java.lang.ThreadLocalDoug Lea的合着者)写道:
"线程池的使用需要极其谨慎.线程池的粗略使用与线程本地的粗略使用相结合可能导致意外的对象保留,正如许多地方所指出的那样."
人们抱怨ThreadLocal与线程池的错误交互,即便如此.但乔希做了制裁:
"性能的每线程实例.Aaron的SimpleDateFormat示例(上图)就是这种模式的一个例子."
ThreadLocal,你可以选择这样做.或者:a)您知道的是,Thread(S),你把值将终止时,您的应用程序完成; 或者b)您可以稍后安排 调用ThreadLocal #set()的相同线程,以便在应用程序终止时调用ThreadLocal #remove()简而言之,决定使用ThreadLocal作为对"每个线程实例池"的快速,无竞争访问的形式并不是一个轻率的决定.
注意:ThreadLocal除了"对象池"之外还有其他用途,这些课程不适用于ThreadLocal只是临时设置的情况,或者存在真正的每线程状态的情况踪迹.
Threre是库实现者的一些后果(即使这些库是项目中的简单实用程序类).
或者:
java.util.concurrent.ThreadLocalRandom,那可能是合适的.(如果你没有实现,Tomcat可能仍会对你的库的用户抱怨java.*).有趣的是要注意java.*使用ThreadLocal技术的规则.要么
LibClass.releaseThreadLocalsForThread()在完成它时调用的线程.但是,让你的图书馆"难以正常使用".
要么
new ExpensiveObjectFactory<T>() { public T get() {...} }如果你认为它真的是必要的话,我可以给你一个".还不错.如果对象真的那么重要并且创建起来很昂贵,那么显式池化可能是值得的.
要么
servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})在未来的JavaEE规范中,看到最后3个项目的标准化会很高兴.
实际上,a的实例化GregorianCalendar非常轻量级.这是不可避免的召唤setTime(),导致大部分工作.它也不会在线程执行的不同点之间保持任何重要状态.把一个Calendar成一个ThreadLocal不可能给你回超过它的成本你...除非绝对分析显示了热点new GregorianCalendar().
new SimpleDateFormat(String)相比之下是昂贵的,因为它必须解析格式字符串.解析后,对象的"状态"对于以后由同一线程使用是很重要的.这更合适.但实例化一个新的可能仍然"比较便宜",而不是给你的课程额外的责任.
由于线程不是由您创建的,它只是由您租用的,我认为在停止使用之前要求对其进行清洁是公平的 - 就像您返回时给租来的汽车加满油箱一样。Tomcat 可以自己清理所有东西,但它会帮你一个忙,提醒你忘记的事情。
ADD:您使用准备好的 GregorianCalendar 的方式是完全错误的:由于服务请求可以并发,并且没有同步,因此doCalc可以由另一个请求getTime调用setTime。引入同步会使事情变慢,因此创建新的GregorianCalendar可能是更好的选择。
换句话说,您的问题应该是:如何保留准备好的GregorianCalendar实例池,以便其数量根据请求率进行调整。因此,至少,您需要一个包含该池的单例。每个 Ioc 容器都有管理单例的方法,并且大多数都有现成的对象池实现。如果您还没有使用 IoC 容器,请开始使用一个(String、Guice),而不是重新发明轮子。
| 归档时间: |
|
| 查看次数: |
15701 次 |
| 最近记录: |