ThreadLocal资源泄漏和WeakReference

我对ThreadLocal的有限理解是它存在资源泄漏问题 。 我收集这个问题可以通过在ThreadLocal中正确使用WeakReferences来解决(尽管我可能误解了这一点。)我只是想要一个模式或示例来正确使用带有WeakReference的ThreadLocal(如果存在)。 例如,在此代码片段中,WeakReference将在何处引入?

static class DateTimeFormatter { private static final ThreadLocal DATE_PARSER_THREAD_LOCAL = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy/MM/dd HH:mmz"); } }; public String format(final Date date) { return DATE_PARSER_THREAD_LOCAL.get().format(date); } public Date parse(final String date) throws ParseException { return DATE_PARSER_THREAD_LOCAL.get().parse(date); } } 

ThreadLocal在内部使用WeakReference 。 如果没有强引用ThreadLocal ,它将被垃圾收集,即使各种线程具有通过该ThreadLocal存储的值。

另外, ThreadLocal值实际存储在Thread ; 如果线程死亡,则收集通过ThreadLocal与该线程关联的所有值。

如果你有一个ThreadLocal作为最终的类成员,那么这是一个强引用,并且在卸载该类之前无法收集它。 但这是任何类成员的工作方式,并不被视为内存泄漏。


更新:引用的问题仅在存储在ThreadLocal的值强引用循环引用的ThreadLocal -sort时才起作用。

在这种情况下,值( SimpleDateFormat )没有向后向ThreadLocal引用。 此代码中没有内存泄漏。

我猜你正在跳过这些箍,因为SimpleDateFormat 不是线程安全的

虽然我知道我上面没有解决你的问题,但我可以建议你看看Joda的日期/时间吗? Joda有一个线程安全的日期/时间格式化机制。 您也不会浪费时间学习Joda API,因为它是新标准日期/时间API提案的基础。

不应该有这样的问题。

线程的ThreadLocal引用被定义为只有相应的线程处于活动状态时才存在(参见javadoc) – 或者换一种方式,一旦线程不活动,如果ThreadLocal是对该对象的唯一引用,那么该对象有资格进行垃圾收集。

所以,要么你发现了一个真正的错误,应该报告它,或者你做错了什么!

我意识到这不是对你的问题的严格答案,但作为一般规则,我不会建议在请求/交互结束时没有明确拆除的情况下使用ThreadLocal 。 经典就是在servlet容器中做这种事情,乍一看似乎很好,但是由于线程被合并,因此即使在处理完每个请求之后, ThreadLocal仍然会挂在资源上。

建议:

  1. 对每次交互使用类似包装器的filter,以在每次交互结束时清除ThreadLocal
  2. 您可以使用SimpleDateFormat的替代方法,例如来自commons-lang或Joda的FastDateFormat,因为有人已经建议
  3. 只需在每次需要时创建一个新的SimpleDateFormat。 我知道这看起来很浪费,但在大多数应用程序中你都不会注意到它们之间的区别

只是为了补充@Neil Coffey所说的,只要你的ThreadLocal实例是静态的,这应该不是问题。 因为你一直在静态实例上调用get(),所以它应该始终对你的简单日期格式化程序保持相同的引用。 因此,正如Neil所说,当Thread终止时,简单日期格式化程序的唯一实例应该有资格进行垃圾回收。

如果你有数字或其他forms的调试显示这引入资源问题,那么这是另一个故事。 但我相信这应该不是问题。

在您的示例中,使用ThreadLocal应该没有问题。

当线程本地设置为由类加载器加载的实例以便稍后卸载时,线程本地(以及单例也!)成为问题。 servlet容器中的典型情况(如tomcat):

  1. webapp在请求处理期间设置本地线程。
  2. 该线程由容器管理。
  3. 应该取消部署webapp。
  4. webapp的类加载器不能被破坏,因为还有一个引用:从本地的线程到实例,再到它的类到它的类加载器。

与单例相同(java.sql.DriverManger和webapp提供的JDBC驱动程序)。

因此,特别是在完全无法控制的环境中,请避免此类事情!

使用后清理本地线程,添加servletfilter:

 protected ThreadLocal myThreadLocal = new ThreadLocal();

 public void doFilter(ServletRequest req,ServletResponse res,chain)抛出IOException,ServletException {
    尝试{
         //设置ThreadLocal(例如,每个请求只存储一次重的计算结果)
         myThreadLocal.set(上下文());

         chain.doFilter(req,res);
     } finally {
         //重要:清理ThreaLocal以防止内存泄漏
         userIsSmartTL.remove();
     }
 }