什么时候应该使用ThreadLocal
变量?
如何使用?
RequestUtils.getCurrentRequestThreadLocal()
。虽然并不是说这很优雅,但这是由于ThreadLocal本身在大多数情况下不是很优雅。
什么时候应该使用ThreadLocal
变量?
如何使用?
RequestUtils.getCurrentRequestThreadLocal()
。虽然并不是说这很优雅,但这是由于ThreadLocal本身在大多数情况下不是很优雅。
Answers:
一种可能的(并且是常见的)用法是,当您有一些不是线程安全的对象,但又希望避免同步对该对象的访问时(我正在看着您,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);
}
}
文件资料。
由于a ThreadLocal
是对给定数据的引用,因此在使用线程池的应用程序服务器中使用s Thread
时,可能会导致类加载泄漏ThreadLocal
。您需要非常小心,以清理ThreadLocal
您的所有内容get()
或set()
使用ThreadLocal
的remove()
方法。
如果完成后没有清理,则它对作为已部署的Webapp的一部分加载的类的任何引用将保留在永久堆中,并且永远不会收集垃圾。重新部署/取消部署Web应用程序不会清除每个Thread
对Web应用程序类的引用,因为Thread
Web应用程序不是您的Web应用程序所拥有的。每个后续部署将创建该类的新实例,该实例将永远不会被垃圾回收。
由于java.lang.OutOfMemoryError: PermGen space
出现一些谷歌搜索之后,您可能最终会遇到内存不足的异常,而这只会增加-XX:MaxPermSize
而不是修复错误。
如果您确实遇到了这些问题,则可以使用Eclipse的Memory Analyzer和/或遵循Frank Kieviet的指南和后续步骤,确定哪个线程和类保留了这些引用。
更新:重新发现了Alex Vasseur的博客条目,该条目帮助我追踪了ThreadLocal
遇到的一些问题。
许多框架使用ThreadLocals来维护一些与当前线程相关的上下文。例如,当当前事务存储在ThreadLocal中时,您不需要通过每个方法调用将它作为参数传递,以防堆栈中有人需要访问它。Web应用程序可以将有关当前请求和会话的信息存储在ThreadLocal中,以便该应用程序可以轻松访问它们。使用Guice,可以在为注入的对象实现自定义范围时使用ThreadLocals (Guice的默认servlet范围很可能也使用它们)。
ThreadLocals是一种全局变量(尽管邪恶程度稍低,因为它们仅限于一个线程),因此在使用它们时应小心避免不必要的副作用和内存泄漏。设计您的API,以便在不再需要ThreadLocal值时将始终自动清除它们,并且不会错误使用该API(例如,如下所示)。ThreadLocals可以用来使代码更清洁,在某些罕见的情况下,他们是做什么工作的唯一方法(我当前的项目有两个这样的情况下,它们都记录在这里下的“静态字段和全局变量”)。
在Java中,如果您的数据可以随每个线程而变化,则您的选择是将该数据传递给需要(或可能需要)该数据的每个方法,或者将该数据与线程相关联。如果您的所有方法都已经需要传递一个公共的“ context”变量,那么将数据传递到任何地方都是可行的。
如果不是这种情况,则可能不希望使用其他参数来使方法签名混乱。在非线程世界中,可以使用等效于Java的全局变量来解决该问题。用线程词来说,全局变量的等效项是线程局部变量。
《Java Concurrency in Practice》一书中有一个很好的例子。作者(Joshua Bloch)在其中解释了如何限制线程是实现线程安全的最简单方法之一,而ThreadLocal是维护线程限制的更正式的手段。最后,他还解释了人们如何将其用作全局变量来滥用它。
我已经从提到的书中复制了文本,但是缺少代码3.10,因为了解在哪里使用ThreadLocal并不是很重要。
线程局部变量通常用于防止基于可变Singleton或全局变量的设计共享。例如,单线程应用程序可能会维护在启动时初始化的全局数据库连接,以避免必须将Connection传递给每个方法。由于JDBC连接可能不是线程安全的,因此使用全局连接而不进行额外协调的多线程应用程序也不是线程安全的。通过使用ThreadLocal来存储JDBC连接,如清单3.10中的ConnectionHolder所示,每个线程将拥有自己的连接。
ThreadLocal被广泛用于实现应用程序框架。例如,在EJB调用期间,J2EE容器将事务上下文与执行线程相关联。这可以通过使用持有事务上下文的静态Thread-Local轻松实现:当框架代码需要确定当前正在运行的事务时,它将从该ThreadLocal获取事务上下文。这很方便,因为它减少了将执行上下文信息传递到每个方法中的需要,但是将使用此机制的任何代码耦合到框架。
通过将ThreadLocal的线程限制属性视为使用全局变量的许可或作为创建“隐藏”方法参数的方法,可以很容易地滥用ThreadLocal。像全局变量一样,线程局部变量会降低可重用性,并在类之间引入隐藏的耦合,因此应谨慎使用。
本质上,当您需要一个变量的值来依赖当前线程时,以其他方式将值附加到线程上并不方便(例如,子类化线程)。
典型的情况是其他一些框架创建了线程代码在其中运行,例如servlet容器,或者使用ThreadLocal更有意义,因为您的变量随后“处于逻辑位置”(而不是变量)从Thread子类或其他哈希映射中挂起)。
在我的网站上,我还进行了一些进一步的讨论,并举例说明了何时使用ThreadLocal可能也会引起您的兴趣。
有人主张使用ThreadLocal作为在某些需要线程号的并发算法中将“线程ID”附加到每个线程的方法(例如,参见Herlihy&Shavit)。在这种情况下,请检查您是否真的受益匪浅!
Java的ThreadLocal在JDK 1.2上引入,但后来在JDK 1.5中被泛化,以在ThreadLocal变量上引入类型安全性。
ThreadLocal可以与Thread范围关联,由Thread执行的所有代码都可以访问ThreadLocal变量,但是两个线程彼此看不到ThreadLocal变量。
每个线程都拥有ThreadLocal变量的互斥副本,在正常情况下或由于任何异常而导致线程结束或死亡后,该线程才有资格进行垃圾回收,因为这些ThreadLocal变量没有任何其他实时引用。
Java中的ThreadLocal变量通常是类中的私有静态字段,并在Thread中维护其状态。
该文件说得很好:“(通过其get或set方法访问[线程局部变量]的每个线程都有其自己的,独立初始化的变量副本”)。
当每个线程必须拥有自己的副本时,可以使用一个。默认情况下,数据在线程之间共享。
可以使用threadlocal变量的两个用例
-1-当我们需要将状态与线程关联时(例如,用户ID或交易ID)。对于Web应用程序,通常会发生这种情况,每个发送到Servlet的请求都具有一个与之关联的唯一transactionID。
// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
请注意,此处的withInitial方法是使用lambda表达式实现的。
2-另一个用例是当我们想要一个线程安全的实例并且我们不想使用同步时,因为同步带来的性能成本更高。一种情况是使用SimpleDateFormat。由于SimpleDateFormat不是线程安全的,因此我们必须提供使其成为线程安全的机制。
public class ThreadLocalDemo1 implements Runnable {
// threadlocal variable is created
private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
return new SimpleDateFormat("dd/MM/yyyy");
}
};
public static void main(String[] args) {
ThreadLocalDemo1 td = new ThreadLocalDemo1();
// Two threads are created
Thread t1 = new Thread(td, "Thread-1");
Thread t2 = new Thread(td, "Thread-2");
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("Thread run execution started for " + Thread.currentThread().getName());
System.out.println("Date formatter pattern is " + dateFormat.get().toPattern());
System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
}
}
从Java 8版本开始,有更多的声明式初始化方式ThreadLocal
:
ThreadLocal<Cipher> 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
被调用时才评估,但先前未评估过值。
您必须非常谨慎地使用ThreadLocal模式。有一些主要的缺点,例如Phil提到的,但没有提到的一个是要确保设置ThreadLocal上下文的代码不是“可重入的”。
当设置信息的代码第二次或第三次运行时,可能会发生不好的事情,因为线程上的信息可能在您不期望的时候开始发生变异。因此,在再次设置之前,请确保未设置ThreadLocal信息。
ThreadLocal
模式。如果您这样做F(){ member=random(); F2(); write(member); }
并且F2用新值覆盖成员,则显然write(member)
将不再写入您已random()
编辑的号码。这实际上只是常识。同样,如果您这样做F(){ F(); }
,那么gd 会遇到无限循环!这在任何地方都是正确的,并非特定于ThreadLocal
。
什么时候?
当一个对象不是线程安全的时,而不是同步会妨碍可伸缩性,请为每个线程分配一个对象,并使其保持线程范围,即ThreadLocal。数据库连接和JMSConnection是最常用但不是线程安全的对象之一。
怎么样 ?
一个示例是Spring框架通过将这些连接对象保留在ThreadLocal变量中而大量使用ThreadLocal来管理后台事务。在较高级别,启动事务时,它将获得连接(并禁用自动提交),并将其保留在ThreadLocal中。在进一步的数据库调用中,它使用相同的连接与数据库通信。最后,它从ThreadLocal获取连接并提交(或回滚)事务并释放连接。
我认为log4j还使用ThreadLocal维护MDC。
ThreadLocal
当您希望拥有某种状态时,这些状态不应该在不同线程之间共享,但是应该在每个线程的整个生命周期中都可以访问它,这很有用。
例如,假设有一个Web应用程序,其中每个请求由不同的线程处理。想象一下,对于每个请求,您都需要多次数据,这对于计算而言是非常昂贵的。但是,每个传入请求的数据可能都已更改,这意味着您不能使用普通缓存。一个简单,快速的解决方案是使用一个ThreadLocal
变量来保存对该数据的访问权限,这样您就不必为每个请求计算一次。当然,也可以不使用来解决此问题ThreadLocal
,但我出于说明目的设计了它。
也就是说,请记住ThreadLocal
s本质上是一种全球状态。结果,它具有许多其他含义,只有在考虑了所有其他可能的解决方案之后才应使用它。
ThreadLocal
用作对象字段...
ThreadLocal
,在全球范围内都可以使用它。如您所说,它仍然可以用作限制可见性的类字段。
这里没有什么新鲜的东西,但是我今天发现,ThreadLocal
在Web应用程序中使用Bean验证时,这非常有用。验证消息已本地化,但默认情况下使用Locale.getDefault()
。您可以Validator
用不同的配置MessageInterpolator
,但是无法Locale
在调用时指定validate
。所以,你可以创建一个静态ThreadLocal<Locale>
(或更好,但与其他事物一般的容器,你可能需要ThreadLocal
再有您的自定义MessageInterpolator
挑Locale
的这一点。下一步是写一个ServletFilter
,它使用一个会话值或request.getLocale()
挑区域和存储它供您ThreadLocal
参考。
ThreadLocal将确保非同步方法中多个线程对可变对象的访问是同步的,这意味着使可变对象在方法内是不可变的。
这是通过为每个线程尝试访问它提供新的可变对象实例来实现的。因此,它是每个线程的本地副本。这是在使实例变量像本地变量一样被访问的方法中的一种技巧。您知道方法局部变量仅对线程可用,其中一个区别是:一旦方法执行结束,方法本地变量将对线程不可用,因为与线程本地共享的可变对象将在多个方法之间可用,直到我们将其清除为止。
根据定义:
Java中的ThreadLocal类使您可以创建只能由同一线程读写的变量。因此,即使两个线程正在执行相同的代码,并且该代码具有对ThreadLocal变量的引用,那么两个线程也无法看到彼此的ThreadLocal变量。
Thread
java中的每个都包含ThreadLocalMap
在其中。
哪里
Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.
实现ThreadLocal:
现在为ThreadLocal创建一个包装器类,该包装器类将容纳如下所示的可变对象(带有或不带有initialValue()
)。
现在,此包装的getter和setter将在threadlocal实例上工作,而不是在可变对象上工作。
如果threadlocal的getter()在的threadlocalmap中找不到任何值Thread
。然后它将调用initialValue()以获取其相对于线程的私有副本。
class SimpleDateFormatInstancePerThread {
private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
UUID id = UUID.randomUUID();
@Override
public String toString() {
return id.toString();
};
};
System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
return dateFormat;
}
};
/*
* Every time there is a call for DateFormat, ThreadLocal will return calling
* Thread's copy of SimpleDateFormat
*/
public static DateFormat getDateFormatter() {
return dateFormatHolder.get();
}
public static void cleanup() {
dateFormatHolder.remove();
}
}
现在wrapper.getDateFormatter()
将调用threadlocal.get()
,并将检查currentThread.threadLocalMap
包含该(线程本地)实例。
如果是,则返回对应的threadlocal实例的值(SimpleDateFormat),
否则使用该threadlocal实例initialValue()添加映射。
因此,在该可变类上实现了线程安全性;每个线程都在使用自己的可变实例,但使用相同的ThreadLocal实例。意味着所有线程将与键共享相同的ThreadLocal实例,但将不同的SimpleDateFormat实例作为值共享。
https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java
线程局部变量通常用于防止基于可变Singleton或全局变量的设计共享。
不使用连接池时,可以在为每个线程建立单独的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的使用。
Java中的ThreadLocal类使您可以创建只能由同一线程读写的变量。因此,即使两个线程正在执行相同的代码,并且该代码具有对ThreadLocal变量的引用,那么两个线程也无法看到彼此的ThreadLocal变量。
在多线程代码中有3种使用类辅助程序(如SimpleDateFormat)的方案,最好的一种是使用ThreadLocal
情境
1-通过锁定或同步机制使用类似共享对象,这会使应用程序变慢
2-在方法内部用作本地对象
在这种情况下,如果我们有4个线程,每个线程调用一个方法1000次,那么我们将创建
4000个 SimpleDateFormat 对象,并等待GC擦除它们
3-使用ThreadLocal
如果我们有4个线程,并且给每个线程一个SimpleDateFormat实例,
那么我们就有4个线程,4个SimpleDateFormat 对象。
不需要锁定机制以及对象的创建和销毁。(良好的时间复杂度和空间复杂度)
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();
}
}
Threadlocal提供了一种非常简单的方法来以零成本实现对象的可重用性。
我遇到的情况是,每个更新通知中都有多个线程正在创建可变缓存的映像。
我在每个线程上使用了Threadlocal,然后每个线程只需要重置旧映像,然后在每个更新通知中从缓存中再次对其进行更新。
来自对象池的常规可重用对象具有与之相关联的线程安全成本,而这种方法则没有。
尝试以下小示例,以了解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”。