如何根据Sonar清理Java ThreadLocals?

Rob*_*ume 8 java memory-leaks thread-local number-formatting sonarqube

自 2019 年 8 月 21 日起可用的声纳规则 ( squid:S5164 / RSPEC-5164 ) 要求在不再使用时清理“ThreadLocal”变量。因此,让我们学习以下类(兼容 JDK6):

public class ThreadLocalExample {

    private static final ThreadLocal<NumberFormat> formats = new ThreadLocal<NumberFormat>() {
        @Override
        protected NumberFormat initialValue() {
            final NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
            nf.setMinimumFractionDigits(2);
            nf.setMaximumFractionDigits(2);
            nf.setGroupingUsed(false);
            return nf;
        }
    };

    public static NumberFormat getFormatter() {
        return formats.get();
    }
}
Run Code Online (Sandbox Code Playgroud)

声纳报告一个重要的错误ThreadLocal声明,在以下的说明:

不再使用时应清除“ThreadLocal”变量

ThreadLocal一旦持有线程不再活动,变量应该被垃圾收集。重新使用持有线程时可能会发生内存泄漏,使用线程池的应用程序服务器就是这种情况。

为避免此类问题,建议始终ThreadLocal使用remove()清除当前线程的ThreadLocal变量值的方法清理 变量。

现在,我采用这种ThreadLocal方法是为了NumberFormat尽可能多地重用实例,避免每次调用创建一个实例,所以我认为如果我remove()在代码中的某个地方调用,我将失去这个解决方案的所有优点。我错过了什么吗?非常感谢。

dav*_*xxx 7

声纳就在这里。

每个线程都有自己的ThreadLocal状态,因此有自己的NumberFormat.
因此,在一般情况下,不从状态中清除数据可能是不希望的,因为线程可能被重用(由服务器回收)并且为先前客户端赋值的状态可能与当前客户端不一致。
例如,某些客户端可以具有 format US,其他客户端可以具有 format FR,等等...除了某些线程可以实例化 ThreadLocal 类之外,其他线程则不能。但是,如果不清理状态,状态仍将为可能不需要它们的线程使用内存。

好吧,在您的代码中,ThreadLocal由于您为任何实例设置了状态,所以状态不存在可变性,因此不可能出现不一致的风险,只是内存“浪费”。

现在,我采用了 ThreadLocal 方法,以便尽可能地重用 NumberFormat 实例,避免每次调用创建一个实例

您可以ThreadLocal根据线程请求重用状态。
因此,如果有 50 个线程,则有 50 个状态。
在 Web 应用程序中,服务器将客户端 HTTP 请求映射到一个线程。
因此,您不会仅在 1 个 http 请求的范围内创建格式化程序的多个实例。这意味着如果您在请求处理中使用格式化程序一两次,缓存ThreadLocal不会带来很大的价值。但如果你用的多了,用起来还是有意义的。

所以我想如果我在代码中的某个地方调用remove(),我将失去这个解决方案的所有优点

如果在请求remove()处理完成后进行调用,则不会影响性能。您不会失去任何优势,因为您可能在请求范围内使用格式化程序数十次,并且只有在最后才会被清理。

您在 servlet 规范中有请求侦听器: https://docs.oracle.com/javaee/7/api/javax/servlet/ServletRequestListener.html
你可以在void requestDestroyed(ServletRequestEvent sre).