什么时候应该使用ThreadLocal变量?

它是如何使用的?


当前回答

由于ThreadLocal是对给定线程中的数据的引用,因此在使用线程池的应用服务器中使用ThreadLocal时,可能会导致类加载泄漏。在使用ThreadLocal的remove()方法清理你get()或set()的任何ThreadLocals时,你需要非常小心。

如果你在完成时不清理,它持有的任何类的引用作为部署的webapp的一部分将保留在永久堆中,永远不会被垃圾收集。重新部署/取消部署webapp不会清除每个线程对你的webapp类的引用,因为线程不是你的webapp所拥有的。每次后续部署都将创建该类的新实例,该实例永远不会被垃圾收集。

由于java.lang.OutOfMemoryError: PermGen空间,你最终会出现内存不足的异常,在谷歌搜索之后可能只是增加-XX:MaxPermSize,而不是修复这个错误。

如果您最终遇到了这些问题,您可以通过使用Eclipse的Memory Analyzer和/或遵循Frank Kieviet的指南和后续内容来确定哪个线程和类保留了这些引用。

更新:重新发现Alex Vasseur的博客条目,它帮助我找到了一些我遇到的ThreadLocal问题。

其他回答

自Java 8发布以来,有更多的声明性方法来初始化ThreadLocal:

ThreadLocal<String> local = ThreadLocal.withInitial(() -> "init value");

在Java 8发布之前,你必须做以下事情:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

此外,如果用于ThreadLocal的类的实例化方法(构造函数,工厂方法)不接受任何参数,您可以简单地使用方法引用(在Java 8中引入):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}
    
ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

注意: 计算是惰性的,因为你传递的java.util.function.Supplier lambda只在调用ThreadLocal#get但value之前没有计算时才会计算。

什么时候?

当一个对象不是线程安全的时候,为了避免影响可伸缩性的同步,给每个线程一个对象,并保持它的线程作用域,即ThreadLocal。最常用但不是线程安全的对象之一是数据库Connection和JMSConnection。

如何?

例如,Spring框架通过将这些连接对象保存在ThreadLocal变量中,在幕后大量使用ThreadLocal来管理事务。在高层,当事务启动时,它获得连接(并禁用自动提交)并将其保存在ThreadLocal中。在进一步的db调用中,它使用相同的连接与db通信。最后,它从ThreadLocal获取连接,提交(或回滚)事务并释放连接。

我认为log4j也使用ThreadLocal来维护MDC。

文档说得很好:“每个访问[线程局部变量]的线程(通过它的get或set方法)都有它自己的、独立初始化的变量副本”。

当每个线程必须有自己的某个副本时,可以使用一个。默认情况下,数据在线程之间共享。

Java中的ThreadLocal类使您能够创建只能由同一个线程读写的变量。因此,即使两个线程正在执行相同的代码,并且该代码有一个对ThreadLocal变量的引用,那么两个线程也不能看到彼此的ThreadLocal变量。

阅读更多

Threadlocal提供了一种非常简单的零成本实现对象可重用性的方法。

我遇到过这样一种情况,在每次更新通知时,多个线程都在创建可变缓存的映像。

我在每个线程上使用Threadlocal,然后每个线程只需要重置旧映像,然后在每次更新通知时从缓存中再次更新它。

来自对象池的通常可重用对象具有与之相关的线程安全成本,而此方法没有。