什么时候应该使用ThreadLocal变量?
它是如何使用的?
什么时候应该使用ThreadLocal变量?
它是如何使用的?
当前回答
当您希望某些状态不应该在不同线程之间共享,但每个线程在其整个生命周期内都可以访问时,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本质上是一种全局状态的形式。因此,它有许多其他含义,只有在考虑了所有其他可能的解决方案后才应该使用。
其他回答
试试这个小例子,感受一下ThreadLocal变量:
public class Book implements Runnable {
private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);
private final String bookName; // It is also the thread's name
private final List<String> words;
public Book(String bookName, List<String> words) {
this.bookName = bookName;
this.words = Collections.unmodifiableList(words);
}
public void run() {
WORDS.get().addAll(words);
System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
}
public static void main(String[] args) {
Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
t1.start();
t2.start();
}
}
控制台输出,如果线程BookA先执行: 结果BookA: 'wordA1, wordA2, wordA3'。 结果BookB: 'wordB1, wordB2'。 控制台输出,如果先执行线程BookB: 结果BookB: 'wordB1, wordB2'。 结果BookA: 'wordA1, wordA2, wordA3'。
缓存,有时你必须计算相同的值很多时间,所以通过存储最后一组输入到一个方法和结果,你可以加快代码。通过使用线程本地存储,您可以避免考虑锁定问题。
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();
}
}
线程局部变量通常用于防止基于线程的设计中的共享 可变单例或全局变量。
在不使用连接池的情况下,它可以用于为每个线程建立单独的JDBC连接等场景。
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
当您调用getConnection时,它将返回与该线程关联的连接。对于其他属性(如dateformat、事务上下文等),您不希望在线程之间共享,也可以这样做。
您也可以使用局部变量来实现相同的目的,但是这些资源通常会占用创建时间,因此您不希望在使用它们执行某些业务逻辑时一次又一次地创建它们。然而,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问题。