何时以及如何使用ThreadLocalvariables?

什么时候应该使用ThreadLocalvariables?

它是如何使用的?

一种可能的(也是常见的)用法是当你有一些不是线程安全的对象,但是你想避免同步对象的访问(我看着你, 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是给定Thread内数据的引用,因此在使用线程池的应用程序服务器中使用ThreadLocal时,最终可能会加载类泄漏。 你需要非常小心地使用ThreadLocalremove()方法清理你get()set()ThreadLocal

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

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

如果您最终遇到这些问题,您可以使用Eclipse的内存分析器和/或遵循Frank Kieviet的指导和跟进来确定哪个线程和类保留了这些引用。

更新:重新发现亚历克斯Vasseur的博客条目 ,帮助我追踪我遇到的一些ThreadLocal问题。

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

ThreadLocals是一种全局variables(尽pipe稍微不那么邪恶,因为它们被限制在一个线程中),所以在使用ThreadLocals时应该小心,以避免不必要的副作用和内存泄漏。 devise你的API,以便在不再需要ThreadLocal值的时候自动清除ThreadLocal值,并且API的错误使用是不可能的(例如像这样 )。 ThreadLocals可以用来使代码更清洁,在极less数情况下,它们是使某些工作成功的唯一方法(我当前的项目有两个这样的例子,它们在“静态字段和全局variables”下面有logging)。

在Java中,如果有一个数据可以随每个线程而变化,那么您的select是将该数据传递给需要(或可能需要)的每个方法,或将该数据与该线程相关联。 如果所有方法都需要传递一个共同的“上下文”variables,那么将数据传递到每个地方都是可行的。

如果不是这种情况,您可能不想用另外的参数混淆您的方法签名。 在一个非线程的世界里,你可以用Java的全局variables来解决这个问题。 在一个线程中,全局variables的等价物是一个线程局部variables。

实质上,当你需要一个variables的值来依赖于当前的线程,并且以其他方式 (例如,线程的子类化) 将值附加到线程是不方便的

一个典型的例子就是其他框架已经创build了代码运行的线程 ,例如servlet容器,或者使用ThreadLocal更有意义,因为variables是“在其合理位置”(而不是一个variables挂在一个线程子类或其他哈希映射)。

在我的网站上,我还有一些关于何时使用ThreadLocal的讨论和例子,这些也可能是有趣的。

有些人主张使用ThreadLocal作为在需要线程号码的某些并发algorithm中将“线程ID”附加到每个线程的方法(请参阅Herlihy&Shavit)。 在这种情况下,检查你是否真的得到了好处!

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

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

Webapp服务器可能会保留一个线程池,并且在响应客户端之前应该删除一个ThreadLocal var,因此当前线程可能会被下一个请求重用。

  1. Java中的ThreadLocal已经在JDK 1.2中引入了,但后来在JDK 1.5中进行了基因化以在ThreadLocalvariables中引入types安全性。

  2. ThreadLocal可以与Thread作用域相关联,Thread所执行的所有代码都可以访问ThreadLocalvariables,但是两个线程不能看到其他ThreadLocalvariables。

  3. 每个线程拥有ThreadLocalvariables的独占副本,在线程完成或死亡之后,通常或由于任何exception,变得有资格进行垃圾回收,因为这些ThreadLocalvariables没有任何其他活动引用。

  4. Java中的ThreadLocalvariables通常是类中的私有静态字段,并在Thread中保持其状态。

阅读更多信息: http : //javarevisited.blogspot.com/2012/05/how-to-use-threadlocal-in-java-benefits.html#ixzz2XltgbHTK

Java并发实践中有很好的例子。 在哪里他解释了如何使用线程约束是实现线程安全的最简单方法之一, 线程本地是维护线程约束的更正式方法。 最后,他还解释了人们如何滥用它作为全局variables。

我已经复制了上述书中的文本,但代码3.10丢失了,因为了解Thread Local应该在哪里使用并不重要。

线程局部variables通常用于防止基于可变单例或全局variables的devise中的共享。 例如,单线程应用程序可能会维护全局数据库连接,该连接在启动时被初始化,以避免必须将连接传递给每个方法。 由于JDBC连接可能不是线程安全的,因此使用全局连接而没有附加协调的multithreading应用程序也不是线程安全的。 通过使用ThreadLocal来存储JDBC连接,如清单3.10中的ConnectionHolder所示,每个线程都有自己的连接。

ThreadLocal广泛用于实现应用程序框架。 例如,J2EE容器在EJB调用期间将事务上下文与正在执行的线程相关联。 这很容易实现,使用一个静态的Thread-Local来保存事务上下文:当框架代码需要确定当前正在运行的事务时,它从这个ThreadLocal中获取事务上下文。 这样做很方便,因为它减less了将执行上下文信息传递到每个方法的需求,但是将使用此机制的任何代码耦合到框架。

通过将ThreadLocal的线程限制属性作为使用全局variables的许可证或作为创build“隐藏的”方法参数的手段来滥用ThreadLocal很容易。 像全局variables一样,线程局部variables可能会降低可重用性,并在类之间引入隐藏的耦合,因此应谨慎使用。

你必须非常小心的ThreadLocal模式。 Phil提到了一些主要的缺陷,但是没有提到的是确保设置ThreadLocal上下文的代码不是“重入”的。

当设置信息的代码第二次或第三次运行时,可能会发生不好的事情,因为线程上的信息可能会在您不期望时发生变异。 因此,请务必确保在重新设置之前尚未设置ThreadLocal信息。

两个可以使用threadlocalvariables的用例 –
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(); } } 

请注意,这里使用lambdaexpression式实现withInitial方法。
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())); } } 

这里没有什么新东西,但我今天发现,在Web应用程序中使用Beanvalidation时, ThreadLocal非常有用。 validation消息是本地化的,但默认使用Locale.getDefault() 。 您可以使用不同的MessageInterpolatorconfigurationValidator ,但在调用validate时无法指定Locale 。 所以你可以创build一个静态的ThreadLocal<Locale> (或者更好的方法是创build一个容器,其中包含其他的东西,你可能需要使用ThreadLocal ,然后让你的定制MessageInterpolator从中selectLocale 。下一步是编写一个ServletFilter , value或request.getLocale()来select语言环境并将其存储在您的ThreadLocal引用中。

正如@unknown(google)所提到的,它的用法是定义一个全局variables,其中引用的值在每个线程中可以是唯一的。 它的用途通常需要存储链接到当前执行线程的某种上下文信息。

我们在Java EE环境中使用它来将用户标识传递给不是Java EE感知的类(不能访问HttpSession或EJB SessionContext)。 这样,使得基于安全的操作使用身份的代码可以从任何地方访问身份,而不必在每个方法调用中明确地传递身份。

大多数Java EE调用中的操作请求/响应循环使得这种用法变得容易,因为它提供了定义好的入口和出口点来设置和取消设置ThreadLocal。

ThreadLocal将确保通过非同步方法中的多个线程访问可变对象是同步的,意味着使可变对象在方法内是不可变的。

这是通过给每个线程的可变对象的新实例尝试访问它来实现的。 所以它是本地复制到每个线程。 这是一个黑客做一个方法的实例variables被访问像局部variables。 当你知道方法局部variables只对线程可用时,一个区别是; 一旦方法执行结束,方法局部variables将不可用于线程,因为与threadlocal共享的可变对象将在多个方法中可用,直到我们清理它为止。

按定义:

Java中的ThreadLocal类使您可以创build只能由同一个线程读取和写入的variables。 因此,即使两个线程正在执行相同的代码,并且代码具有对ThreadLocalvariables的引用,那么两个线程也不能看到彼此的ThreadLocalvariables。

java中的每个Thread都包含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创build一个包装类,它将持有像下面那样的可变对象(有或没有initialValue() )。
现在,这个包装器的getter和setter将在threadlocal实例上工作,而不是在mutable对象上工作。

如果threadlocal的getter()没有在Thread的threadlocalmap中find任何值; 那么它会调用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包含this (threadlocal)实例。
如果是,则返回相应threadlocal实例的值(SimpleDateFormat)
else使用这个线程本地实例initialValue()来添加地图。

在这个可变的类中实现了线程安全。 由每个线程正在使用自己的可变实例,但具有相同的ThreadLocal实例。 意味着所有的线程将共享相同的ThreadLocal实例作为键,但不同的SimpleDateFormat实例作为值。

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java

什么时候?

当一个对象不是线程安全的,而不是妨碍可伸缩性的同步时,给每个线程一个对象,并保持它的线程范围,即ThreadLocal。 数据库连接和JMSConnection是最常用但不是线程安全的对象之一。

怎么样 ?

一个例子就是Spring框架通过将这些连接对象保留在ThreadLocalvariables中来大量使用ThreadLocal来pipe理幕后事务。 在高级别,当一个事务开始时,它获得连接(并禁用自动提交)并将其保存在ThreadLocal中。 在进一步的数据库调用它使用相同的连接与数据库沟通。 最后,它从ThreadLocal获取连接并提交(或回滚)事务并释放连接。

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

当你希望有一些状态不应该在不同的线程之间共享时, ThreadLocal是有用的,但是它应该在每个线程的整个生命周期内都可以访问。

举一个例子,设想一个Web应用程序,每个请求由不同的线程提供服务。 想象一下,对于每个请求,您需要多次数据,这是相当昂贵的计算。 但是,每个传入请求的数据可能已经更改,这意味着您不能使用普通caching。 解决这个问题的一个简单而快速的方法就是让一个ThreadLocalvariables持有这个数据的访问权限,这样你就必须为每个请求计算一次。 当然,这个问题也可以在不使用ThreadLocal情况下解决,但是我为了说明的目的而devise了它。

这就是说,请记住ThreadLocal本质上是一种全局状态。 因此,它有许多其他的含义,只有在考虑到所有其他可能的解决scheme后才能使用。

caching,有时你必须计算相同的价值很多时间,所以通过存储最后一组input到一个方法和结果,你可以加快代码。 通过使用线程本地存储,您不必考虑locking。

ThreadLocal是由JVM专门configuration的function,仅为线程提供隔离的存储空间。 就像实例作用域variables的值只绑定到给定的类实例一样。 每个对象都有其唯一的值,他们不能看到其他的值。 ThreadLocalvariables的概念也是如此,它们是线程本地的,对象实例是其他线程,除了创build它的那个线程之外,看不到它。 看这里

 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(); } }