java 当类暴露给线程池时,清理 ThreadLocal 资源真的是我的工作吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13852632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it really my job to clean up ThreadLocal resources when classes have been exposed to a thread pool?
提问by David Bullock
My use of ThreadLocal
我对 ThreadLocal 的使用
In my Java classes, I sometimes make use of a ThreadLocal
mainly as a means of avoiding unnecessary object creation:
在我的 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();
}
}
I do this for the proper reason - GregorianCalendar
is one of those gloriously stateful, mutable, non-threadsafe objects, which provides a service across multiple calls, rather than representing a value. Further, it is considered to be 'expensive' to instantiate (whether this is true or not is not the point of this question). (Overall, I really admire it :-))
我这样做是有正当理由的——它GregorianCalendar
是有状态的、可变的、非线程安全的对象之一,它提供跨多个调用的服务,而不是表示一个值。此外,实例化被认为是“昂贵的”(这是否属实不是这个问题的重点)。(总的来说,我真的很佩服它:-))
How Tomcat Whinges
Tomcat 如何抱怨
However, if I use such a class in any environment which pools threads - andwhere my application is not in control of the lifecycle of those threads - then there is the potential for memory leaks. A Servlet environment is an good example.
但是,如果我在任何汇集线程的环境中使用这样的类 -并且我的应用程序无法控制这些线程的生命周期 - 那么就有可能发生内存泄漏。Servlet 环境就是一个很好的例子。
In fact, Tomcat 7 whinges like so when a webapp is stopped:
事实上,当 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
严重:Web 应用程序 [] 使用 [org.apache.xmlbeans.impl.store.CharUtil$1] 类型的键(值 [org.apache.xmlbeans.impl.store.CharUtil$1@2aace7a7])和一个值创建了一个 ThreadLocal类型 [java.lang.ref.SoftReference](值 [java.lang.ref.SoftReference@3d9c9ad4])但在 Web 应用程序停止时未能将其删除。线程将随着时间的推移而更新,以尝试避免可能的内存泄漏。2012 年 12 月 13 日下午 12:54:30 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
(Not even my code doing it, in that particular case).
(在那种特殊情况下,甚至我的代码都没有这样做)。
Who is to blame?
谁是罪魁祸首?
This hardly seems fair. Tomcat is blaming me(or the user of my class) for doing the right thing.
这似乎不太公平。Tomcat 责备我(或我班的用户)做正确的事。
Ultimately, that's because Tomcat wants to reuse threads it offered to me, for otherweb apps. (Ugh - I feel dirty.) Probably, it's not a great policy on Tomcat's part - because threads actually do have/cause state - don't share 'em between applications.
归根结底,这是因为 Tomcat 想要将它提供给我的线程重用于其他Web 应用程序。(呃 - 我觉得很脏。)可能,这对 Tomcat 来说不是一个很好的策略 - 因为线程实际上确实有/导致状态 - 不要在应用程序之间共享它们。
However, this policy is at least common, even if it is not desirable. I feel that I'm obliged - as a ThreadLocal
user, to provide a way for my class to 'release' the resources which my class has attached to various threads.
然而,这个政策至少是普遍的,即使它是不可取的。我觉得我有义务——作为一个ThreadLocal
用户,为我的班级提供一种“释放”我班级附加到各种线程的资源的方法。
But what to do about it?
但是该怎么办呢?
What is the right thing to do here?
在这里做什么是正确的?
To me, it seems like the servlet engine's thread-reuse policy is at odds with the intent behind ThreadLocal
.
在我看来,servlet 引擎的线程重用策略似乎与ThreadLocal
.
But maybe I should provide a facility to allow users to say "begone, evil thread-specific state associated with this class, even though I am in no position to let the thread die and let GC do its thing?". Is it even possible for me to do this? I mean, it's not like I can arrange for ThreadLocal#remove()
to be called on each of the Threads which saw ThreadLocal#initialValue()
at some time in the past. Or is there another way?
但是也许我应该提供一个工具来允许用户说“与这个类相关联的邪恶的线程特定状态,即使我无法让线程死亡并让 GC 做它的事情?”。我什至有可能这样做吗?我的意思是,我无法安排在过去某个时间ThreadLocal#remove()
看到的每个线程上调用ThreadLocal#initialValue()
。或者还有其他方法吗?
Or should I just say to my users "go and get yourself a decent classloader and thread pool implementation"?
或者我应该对我的用户说“去给自己找一个像样的类加载器和线程池实现”?
EDIT#1: Clarified how threadCal
is used in a vanailla utility class which is unaware of thread lifecycles
EDIT#2: Fixed a thread safety issue in DateSensitiveThing
编辑#1:阐明了如何threadCal
在不知道线程生命周期的普通实用程序类中使用
编辑#2:修复了线程安全问题DateSensitiveThing
回答by David Bullock
Sigh, this is old news
咳咳,这是旧闻
Well, a bit late to the party on this one. In October 2007, Josh Bloch (co-author of java.lang.ThreadLocal
along with Doug Lea) wrote:
好吧,这个聚会有点晚了。2007 年 10 月,Josh Bloch(java.lang.ThreadLocal
与 Doug Lea 合着)写道:
"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."
“线程池的使用需要格外小心。线程池的草率使用与线程局部变量的草率使用相结合可能会导致意外的对象保留,正如在许多地方所指出的那样。”
People were complaining about the bad interaction of ThreadLocal with thread pools even then. But Josh did sanction:
那时人们还在抱怨 ThreadLocal 与线程池的不良交互。但乔希确实批准了:
"Per-thread instances for performance. Aaron's SimpleDateFormat example (above) is one example of this pattern."
“性能的每线程实例。Aaron 的 SimpleDateFormat 示例(上图)就是这种模式的一个示例。”
Some Lessons
一些教训
- If you put any kind of objects into any object pool, you must provide a way to remove them 'later'.
- If you 'pool' using a
ThreadLocal
, you have limited options for doing that. Either: a) you knowthat theThread
(s) where you put values will terminate when your application is finished; OR b) you can later arrange for same threadthat invoked ThreadLocal#set() to invoke ThreadLocal#remove() whenever your application terminates - As such, your use of ThreadLocal as an object pool is going to exact a heavy price on the design of your application and your class. The benefits don't come for free.
- As such, use of ThreadLocal is probably a premature optimization, even though Joshua Bloch urged you to consider it in 'Effective Java'.
- 如果您将任何类型的对象放入任何对象池,您必须提供一种“稍后”删除它们的方法。
- 如果您使用 'pool'
ThreadLocal
,那么您的选择有限。要么:a)您知道Thread
您放置值的(s)将在您的应用程序完成时终止;或者 b) 您可以稍后安排 调用 ThreadLocal#set() 的同一线程在应用程序终止时调用 ThreadLocal#remove() - 因此,您使用 ThreadLocal 作为对象池将在您的应用程序和类的设计上付出沉重的代价。好处不是免费的。
- 因此,使用 ThreadLocal 可能是一种过早的优化,尽管 Joshua Bloch 在“Effective Java”中敦促您考虑它。
In short, deciding to use a ThreadLocal as a form of fast, uncontended access to "per thread instance pools" is not a decision to be taken lightly.
简而言之,决定使用 ThreadLocal 作为对“每个线程实例池”的快速、无竞争访问的一种形式并不是一个轻率的决定。
NOTE: There are other uses of ThreadLocal other than 'object pools', and these lessons do not apply to those scenarios where the ThreadLocal is only intended to be set on a temporary basis anyway, or where there is genuine per-thread state to keep track of.
注意:除了“对象池”之外,ThreadLocal 还有其他用途,这些课程不适用于 ThreadLocal 无论如何只打算临时设置的情况,或者有真正的每线程状态要保持的情况踪迹。
Consequences for Library implementors
库实现者的后果
Threre are some consequences for library implementors (even where such libraries are simple utility classes in your project).
库实现者会产生一些后果(即使这些库是项目中的简单实用程序类)。
Either:
任何一个:
- You use ThreadLocal, fully aware that you might 'pollute' long-running threads with extra baggage. If you are implementing
java.util.concurrent.ThreadLocalRandom
, it might be appropriate. (Tomcat might still whinge at users of your library, if you aren't implementing injava.*
). It's interesting to note the discipline with whichjava.*
makes sparing use of the ThreadLocal technique.
- 您使用 ThreadLocal,充分意识到您可能会用额外的负担“污染”长时间运行的线程。如果您正在实施
java.util.concurrent.ThreadLocalRandom
,它可能是合适的。(如果您没有在 中实现,Tomcat 可能仍然会对您的库的用户发牢骚java.*
)。有趣的是要注意java.*
避免使用 ThreadLocal 技术的纪律。
OR
或者
- You use ThreadLocal, and give clients of your class/package:
a) the opportunity to choose to forego that optimization ("don't use ThreadLocal ... I can't arrange for cleanup"); AND
b) a way to clean up ThreadLocal resources ("it's OK to use ThreadLocal ... I can arrange for all Threads which used you to invoke
LibClass.releaseThreadLocalsForThread()
when I am finished with them.
- 您使用 ThreadLocal,并为您的类/包的客户提供:a) 有机会选择放弃该优化(“不要使用 ThreadLocal ...我无法安排清理”);并且 b) 一种清理 ThreadLocal 资源的方法(“可以使用 ThreadLocal ......我可以安排所有使用过的线程
LibClass.releaseThreadLocalsForThread()
在我完成后调用它们。
Makes your library 'hard to use properly', though.
但是,使您的库“难以正确使用”。
OR
或者
- You give your clients the opportunity to supply their own object-pool impelementation (which might use ThreadLocal, or synchronization of some sort). ("OK, I can give you a
new ExpensiveObjectFactory<T>() { public T get() {...} }
if you think it is really neccesasry".
- 您让您的客户有机会提供他们自己的对象池实现(可能使用 ThreadLocal 或某种同步)。(“好吧,
new ExpensiveObjectFactory<T>() { public T get() {...} }
如果你认为真的有必要,我可以给你一个”。
Not so bad. If the object are really that important and that expensive to create, explicit pooling is probably worthwhile.
没那么糟糕。如果对象真的那么重要并且创建起来那么昂贵,那么显式池化可能是值得的。
OR
或者
- You decide it's not worth that much to your app anyway, and find a different way to approach the problem. Those expensive-to-create, mutable, non-threadsafe objects are causing you pain ... is using them really the best option anyway?
- 您决定无论如何它对您的应用程序来说没有那么多价值,并找到一种不同的方法来解决这个问题。那些创建成本高昂的、可变的、非线程安全的对象让你很痛苦……无论如何,使用它们真的是最好的选择吗?
Alternatives
备择方案
- Regular object pooling, with all its contended synchronization.
- Not pooling objects - just instantiate them in a local scope and discard later.
- Not pooling threads (unless you can schedule cleanup code when you like) - don't use your stuff in a JaveEE container
- Thread pools which are smart enough to clean-up ThreadLocals without whinging at you.
- Thread pools which allocate Threads on a 'per application' basis, and then let them die when the application is stopped.
- A protocol between thread-pooling containers and applications which allowed registration of a 'application shutdown handler', which the container could schedule to run on Threads which had been used to service the application ... at some point in the future when that Thread was next available. Eg.
servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})
- 常规对象池,及其所有竞争同步。
- 不池对象 - 只需在本地范围内实例化它们并稍后丢弃。
- 不池化线程(除非你可以在你喜欢的时候安排清理代码)——不要在 JaveEE 容器中使用你的东西
- 线程池足够聪明,可以清理 ThreadLocals 而不会对你发牢骚。
- 线程池在“每个应用程序”的基础上分配线程,然后在应用程序停止时让它们死亡。
- 线程池容器和应用程序之间的协议,它允许注册“应用程序关闭处理程序”,容器可以安排在用于服务应用程序的线程上运行......下一个可用。例如。
servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})
It'd be nice to see some standardisation around the last 3 items, in future JavaEE specs.
很高兴在未来的 JavaEE 规范中看到关于最后 3 项的一些标准化。
Bootnote
引导笔记
Actually, instantiation of a GregorianCalendar
is pretty lightweight. It's the unavoidable call to setTime()
which incurs most of the work. It also doesn't hold any significant state between different points of a thread's exeuction. Putting a Calendar
into a ThreadLocal
is unlikely to give you back more than it costs you ... unless profiling definitely shows a hot spot in new GregorianCalendar()
.
实际上, a 的实例化GregorianCalendar
是非常轻量级的。setTime()
大部分工作都是不可避免的调用。它也不会在线程执行的不同点之间保持任何重要状态。将 aCalendar
放入 aThreadLocal
不太可能给您带来比您花费更多的回报……除非分析确实显示了new GregorianCalendar()
.
new SimpleDateFormat(String)
is expensive by comparison, because it has to parse the format string. Once parsed, the 'state' of the object is significant to later uses by the same thread. It's a better fit. But it might still be 'less expensive' to instantiate a new one, than give your classes extra responsibilities.
new SimpleDateFormat(String)
相比之下,它很昂贵,因为它必须解析格式字符串。解析后,对象的“状态”对于同一线程以后的使用很重要。这是一个更好的选择。但是,与为您的类赋予额外的责任相比,实例化一个新的类可能仍然“便宜”。
回答by Alexei Kaigorodov
Since the thread was not created by you, it only was rented by you, I think it is fair to require to clean it before stop using - just as you fills up the tank of a rented car when returning. Tomcat could just clean everything itself, but it does you a favor, reminding of forgot things.
由于线程不是您创建的,它只是您租用的,我认为在停止使用之前要求清洁它是公平的 - 就像您在返回时加满租来的汽车的油箱一样。Tomcat 可以自己清理所有东西,但它会帮你一个忙,提醒忘记的东西。
ADD:
The way you use prepared GregorianCalendar is simply wrong: since service requests can be concurrent, and there is no synchronization, doCalc
can take getTime
ater setTime
invoked by another request. Introducing synchronization would make things slow, so that creating a new GregorianCalendar
could be a better option.
ADD:您可以使用准备的GregorianCalendar的方法是完全错误的:因为服务请求可以是并发的,并没有同步,doCalc
可以采取getTime
亚特setTime
由其它请求调用。引入同步会使事情变慢,因此创建一个新的GregorianCalendar
可能是更好的选择。
In other words, your question should be: how to keep pool of prepared GregorianCalendar
instances so that its number is adjusted to request rate. So as a minimum, you need a singleton which contains that pool. Each Ioc container has means to manage a singleton, and most have ready object pool implementations. If you do not yet use an IoC container, start to use one (String, Guice), rather than reinvent the wheel.
换句话说,您的问题应该是:如何保留准备好的GregorianCalendar
实例池,以便根据请求率调整其数量。因此,至少,您需要一个包含该池的单例。每个 Ioc 容器都有管理单例的方法,并且大多数都有现成的对象池实现。如果您还没有使用 IoC 容器,请开始使用一个 (String, Guice),而不是重新发明轮子。
回答by Adam Gent
If its any help I use a custom SPI (an interface) and the JDK ServiceLoader
. Then all of my various internal libraries (jars) that need to do unloading of threadlocals just follow the ServiceLoader pattern. So if a jar needs threadlocal cleanup it will automatically get picked if it has the appropriate /META-INF/services/interface.name
.
如果有帮助,我会使用自定义 SPI(接口)和 JDK ServiceLoader
。然后我所有需要卸载 threadlocals 的各种内部库(jar)都遵循 ServiceLoader 模式。因此,如果 jar 需要线程局部清理,如果它具有适当的/META-INF/services/interface.name
.
Then I do the unloading in a filter or listener (I had some issues with listeners but I can't recall what).
然后我在过滤器或监听器中进行卸载(我对监听器有一些问题,但我不记得是什么)。
It would be ideal if the the JDK/JEE came with a standardSPI for clearing threadlocals.
如果 JDK/JEE 带有用于清除线程局部变量的标准SPI,那将是理想的。
回答by Evgeniy Dorofeev
I think JDK's ThreadPoolExecutor could do ThreadLocals cleaning after task execution but as we know it doesn't. I think it could have provided at least an option. The reason why might be because Thread provides only package private access to its TreadLocal maps, so ThreadPoolExecutor just cannot access them without changing Thread's API.
我认为 JDK 的 ThreadPoolExecutor 可以在任务执行后进行 ThreadLocals 清理,但我们知道它不会。我认为它至少可以提供一个选择。原因可能是因为 Thread 仅提供对其 TreadLocal 映射的包私有访问,因此 ThreadPoolExecutor 无法在不更改 Thread API 的情况下访问它们。
Interestingly, ThreadPoolExecutor has protected method stubs beforeExecution
and afterExecution
, API says: These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals...
. So I can imagine a Task that implements a ThreadLocalCleaner interface and our custom ThreadPoolExecutor that on afterExecution calls task's cleanThreadLocals();
有趣的是,ThreadPoolExecutor 有保护方法存根beforeExecution
和afterExecution
,API 说:These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals...
。所以我可以想象一个实现 ThreadLocalCleaner 接口的 Task 和我们自定义的 ThreadPoolExecutor 在 afterExecution 上调用 task 的 cleanThreadLocals();
回答by David Bullock
After thinking about this for a year, I've decided it is not acceptable for a JavaEE container to share pooled worker threads between instances of un-related applications. This is not 'enterprise' at all.
在考虑了一年之后,我认为 JavaEE 容器在不相关的应用程序实例之间共享池化工作线程是不可接受的。这根本就不是“企业”。
If you're really going to share threads around, java.lang.Thread
(in a JavaEE environment, at least) should support methods like setContextState(int key)
and forgetContextState(int key)
(mirroring setClasLoaderContext()
), which permit the container to isolate application-specific ThreadLocal state, as it hands the thread between various applications.
如果您真的要共享线程,java.lang.Thread
(至少在 JavaEE 环境中)应该支持setContextState(int key)
和forgetContextState(int key)
(镜像setClasLoaderContext()
)之类的方法,这允许容器隔离特定于应用程序的 ThreadLocal 状态,因为它在各种应用程序之间处理线程。
Pending such modifications in the java.lang
namespace, it is only sensible for application deployers to adopt a 'one thread-pool, one instance of related applications' rule, and for application developers to assume that 'this thread is mine until ThreadDeath us do part'.
等待java.lang
命名空间中的此类修改,只有应用程序部署者采用“一个线程池,相关应用程序的一个实例”规则才是明智的,并且应用程序开发人员假设“这个线程是我的,直到 ThreadDeath 我们参与”。