何时使用以及如何使用 ThreadLocal 变量?

问题

我什么时候应该使用ThreadLocal变量?它应该如何使用?


#1 热门回答(771 赞)

一种常见的用法是当你有一些不是线程安全的对象时,你想避免对该对象的并发访问(如:SimpleDateFormat),而是给每个线程自己的对象实例。

例如:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

Documentation


#2 热门回答(377 赞)

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

如果在完成时没有清理,那么对于作为已部署Web应用程序的一部分加载的类的引用将保留在permanent heap中,并且永远不会收集垃圾。重新部署/取消部署 webapp 不会清理每个Thread 对你的 webapp 类的引用,因为Thread不是你的 webapp 所拥有的。每个连续的部署将创建一个不会被垃圾收集的类的新实例。

由于java.lang.OutOfMemoryError:PermGen space,最终导致内存不足,并且在一些 googling 之后可能只会增加-XX:MaxPermSize而不是修复bug。

如果您最终遇到这些问题,则可以使用Eclipse's Memory Analyzer或跟随Frank Kieviet's guidefollowup来确定哪个线程和类保留了这些引用。

更新:

重新发现Alex Vasseur's blog entry,帮助我追踪我遇到的一些ThreadLocal问题。


#3 热门回答(122 赞)

许多框架使用 ThreadLocals 来维护与当前线程相关的一些上下文。例如,当当前事务存储在ThreadLocal中时,您无需通过每个方法调用将其作为参数传递,以防堆栈中的某人需要访问它。 Web应用程序可能会将有关当前请求和会话的信息存储在ThreadLocal中,以便应用程序可以轻松访问它们。使用Guice时,可以在为注入对象实现custom scopes时使用ThreadLocals(Guice的defaultservlet scopes最可能也使用它们)。

ThreadLocals是一种全局变量(尽管稍微不那么邪恶,因为它们仅限于一个线程),所以在使用它们以避免不必要的副作用和内存泄漏时应该小心。设计你的 API,以便在不再需要 ThreadLocal 值时,将始终自动清除ThreadLocal值,并且不可能错误地使用API​​(例如like this)。 ThreadLocals可以用来使代码更清洁,在某些罕见的情况下,他们是做什么工作的唯一方法(我当前的项目有两个这样的情况下,他们是documented here under “静态字段和全局变量”)。