什么时候应该使用ThreadLocal变量?
它是如何使用的?
什么时候应该使用ThreadLocal变量?
它是如何使用的?
当前回答
您必须非常小心地使用ThreadLocal模式。有一些主要的缺点,如Phil所提到的,但其中一个没有提到的是,要确保设置ThreadLocal上下文的代码不是“可重入的”。
当设置信息的代码第二次或第三次运行时,可能会发生糟糕的事情,因为线程上的信息可能会在您没有预料到的时候开始发生变化。因此,在再次设置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问题。
一种可能的(也是常见的)用途是,当您有一些不是线程安全的对象,但您希望避免同步访问该对象(我正在看您,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);
}
}
文档。
ThreadLocal是JVM专门提供的功能,仅为线程提供隔离的存储空间。与实例作用域变量的值一样,变量只能绑定到类的给定实例。每个对象都有其唯一的值,它们不能看到彼此的值。ThreadLocal变量的概念也是如此,在对象实例的意义上,它们是线程的本地线程,除了创建它的线程之外,其他线程看不到它。在这里看到的
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) {
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
}
}
在Java中,如果您有一个每个线程都可以变化的数据,那么您可以选择将该数据传递给每个需要(或可能需要)它的方法,或者将该数据与线程关联。如果你的所有方法都需要传递一个公共的“上下文”变量,那么到处传递数据可能是可行的。
如果不是这样,您可能不希望用额外的参数来打乱方法签名。在非线程环境中,可以使用Java中等价的全局变量来解决这个问题。在线程词中,与全局变量等价的是线程局部变量。
当您希望某些状态不应该在不同线程之间共享,但每个线程在其整个生命周期内都可以访问时,ThreadLocal非常有用。
As an example, imagine a web application, where each request is served by a different thread. Imagine that for each request you need a piece of data multiple times, which is quite expensive to compute. However, that data might have changed for each incoming request, which means that you can't use a plain cache. A simple, quick solution to this problem would be to have a ThreadLocal variable holding access to this data, so that you have to calculate it only once for each request. Of course, this problem can also be solved without the use of ThreadLocal, but I devised it for illustration purposes.
也就是说,请记住ThreadLocals本质上是一种全局状态的形式。因此,它有许多其他含义,只有在考虑了所有其他可能的解决方案后才应该使用。