19

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

static class DateTimeFormatter {
    private static final ThreadLocal<SimpleDateFormat> DATE_PARSER_THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
        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);
    }
}
4

8 回答 8

36

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

此外,ThreadLocal值实际上存储在Thread; 如果一个线程死亡,所有通过 a 与该线程关联的值都会ThreadLocal被收集。

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


更新:ThreadLocal仅当存储在强引用中的值(ThreadLocal类似于循环引用)时,引用的问题才会发挥作用。

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

于 2009-06-02T16:57:33.443 回答
11

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

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

于 2009-06-02T16:45:51.147 回答
4

应该有这样的问题。

线程的 ThreadLocal 引用被定义为只存在于对应的线程还活着(参见 javadoc)——或者换句话说,一旦线程不活着,如果 ThreadLocal 是对该对象的唯一引用,那么该对象有资格进行垃圾收集。

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

于 2009-06-02T16:35:21.490 回答
3

我意识到这并不是对您问题的严格回答,但作为一般规则,我不建议使用ThreadLocal在请求/交互结束时没有明确拆除的环境。经典的做法是在 servlet 容器中执行此类操作,乍一看似乎还不错,但由于线程是池化的,因此ThreadLocal即使在处理完每个请求后,挂在资源上也成为问题。

建议:

  1. 对每次交互使用类似包装器的过滤器,以在每次交互结束时清除 ThreadLocal
  2. 您可以使用 SimpleDateFormat 的替代方法,例如commons-lang或 Joda 中的 FastDateFormat,正如有人已经建议的那样
  3. 只需在每次需要时创建一个新的 SimpleDateFormat。我知道这似乎很浪费,但在大多数应用程序中你不会注意到差异
于 2009-06-02T17:35:57.200 回答
1

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

如果您有数字或其他形式的调试来显示此引入资源问题,那就是另一回事了。但我相信它不应该是一个问题。

于 2009-06-02T16:40:31.827 回答
0

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

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

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

单例(webapp 提供的 java.sql.DriverManger 和 JDBC 驱动程序)也是如此。

因此,尤其是在不受您完全控制的环境中,请避免此类事情!

于 2009-06-02T16:56:47.623 回答
0

使用后清理本地线程,添加 servlet 过滤器:

protected ThreadLocal myThreadLocal = new ThreadLocal();

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

        链.doFilter(req, res);
    } 最后 {
        // 重要:清理 ThraLocal 以防止内存泄漏
        userIsSmartTL.remove();
    }
}
于 2013-12-23T12:53:50.547 回答
0

上面的代码示例没有问题,因为它在静态变量中使用了 ThreadLocal。

当您将 ThreadLocals 实例初始化为非静态变量时,会出现 ThreadLocal 内存泄漏的一个问题。当持有该变量的对象被垃圾回收时,ThreadLocal 的引用保留在线程中。如果您随后在某种循环中实例化并使用许多 ThreadLocals,则会出现内存泄漏。

我在netty的FastThreadLocal上遇到了这个问题(我猜java ThreadLocal应该有同样的问题)。我的解决方案是在 ThreadLocal 中使用弱引用映射值来解决此问题。这允许使用 ThreadLocal 变量作为实例变量,当持有对象被释放时,这些变量可以被垃圾回收。

这里的代码(可以用来代替ThreadLocals): https ://github.com/invesdwin/invesdwin-util/blob/master/invesdwin-util-parent/invesdwin-util/src/main/java/de/invesdwin /util/concurrent/reference/WeakThreadLocalReference.java

于 2021-04-02T21:06:24.760 回答