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

847 java concurrency multithreading thread-local thread-confinement

ThreadLocal什么时候应该使用变量?

怎么用?

ove*_*ink 847

一种可能的(和常见的)用法是当你有一些不是线程安全的对象,但是你想避免同步对该对象的访问(我正在看你,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

文档.

  • upvote for"(我在看着你,SimpleDateFormat)" (198认同)
  • 同步或threadlocal的另一种替代方法是使变量成为局部变量.本地变量始终是线程安全的.我认为将DateFormats设置为本地是不好的做法,因为它们的创建成本很高,但我从未见过关于此主题的可靠指标. (153认同)
  • 这是黑客`SimpleDateFormat`的高价格.也许最好使用[线程安全的替代方案](http://stackoverflow.com/questions/10411944/java-text-simpledateformat-not-thread-safe).如果你同意[单身人士不好](http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons)那么`ThreadLocal`就更糟了. (14认同)
  • @overthink 有什么理由声明 ThreadLocal 是静态的和最终的,我的意思是性能还是什么? (4认同)
  • ThreadLocal.get()方法将为每个线程调用ThreadLocal.initialValue()(一次),这意味着为每个线程创建一个SimpleDateFormat对象.将SimpleDateFormat作为局部变量(因为我们不必处理垃圾收集问题)不是更好吗? (3认同)
  • 请注意不要对SimpleDateFormat使用双括号初始化,因为这会创建一个匿名类,因此您的类加载器不能被垃圾回收.内存泄漏:返回新的SimpleDateFormat(){{applyPattern("yyyyMMdd HHmm")}}; (2认同)
  • @Pacerier 成员变量如何相关?他说的是局部变量,这意味着每次方法调用都会创建新的“SimpleDateFormat”,这是一种浪费。线程本地要好得多,因为它很灵活,并且内存“泄漏”几乎不明显,并且无论如何都可以在线程被销毁时进行管理。成员变量“SimpleDateFormat”不好,因为它不是线程安全的! (2认同)

Phi*_*l M 414

由于a ThreadLocal是对给定数据的引用,因此在使用线程池的应用程序服务器中使用s Thread时,最终会导致类加载泄漏ThreadLocal.你必须非常小心清理任何ThreadLocal是你get()set()使用ThreadLocalremove()方法.

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

由于java.lang.OutOfMemoryError: PermGen space和一些谷歌搜索之后可能会增加-XX:MaxPermSize而不是修复错误,你最终会因内存不足而异常.

如果您最终遇到这些问题,可以使用Eclipse的Memory Analyzer和/或遵循Frank Kieviet的指南后续内容来确定哪个线程和类保留了这些引用.

更新:重新发现了Alex Vasseur的博客文章,帮助我找到了ThreadLocal我遇到的一些问题.

  • 这是一个非常多的赞成答案,虽然提供信息,但实际上并没有真正回答这个问题. (21认同)
  • Julien链接到的帖子已经转移到[here](http://jsr166-concurrency.10961.n7.nabble.com/Threadlocals-and-memory-leaks-in-J2EE-td3960.html)似乎是_well值得一读...... _ (11认同)
  • Alex Vasseur移动了他的博客.[here](http://avasseur.blogspot.com/2003/11/threadlocal-and-memory-leaks.html)是内存泄漏文章的当前链接. (10认同)
  • @Robin:我不同意.问题是关于如何正确使用ThreadLocal,以完全理解任何概念(ThreadLocal在这里),了解如何不使用它以及不小心使用它的风险也很重要,这就是Phil的答案.我很高兴他涵盖了其他答案中没有涉及的那一点.所有这些选票都是当之无愧的.我相信SO应该建立对概念的理解,而不仅仅是成为QA网站. (9认同)
  • 既然PermGen被Java 8杀死了,这会以任何方式改变这个答案吗? (6认同)
  • 如果ThreadPool使线程保持活动状态,那么它们的ThreadLocals也会保留,但是当再次分配池中的线程时,它们将被新值覆盖吗?为什么会泄漏?在我看来,您可能已经确定增加了内存,等于num thread *线程局部值的大小,但是您的ThreadPool应该将自身管理到合理的大小,因此这并不是内存泄漏,而是增加了MaxPermSize实际上应该是一个很好的解决方案。 (2认同)
  • 我相信答案不再相关,因为`Thread` 使用`WeakReference&lt;ThreadLocal&gt;`。因此,当关联的“ThreadLocal”被“gc”处理时,Map 还将在下一次重新散列期间删除陈旧的值。 (2认同)
  • @Robin 的回答不一定完全是对方所说的,如果已经说过的话,那么附加信息比再次写同样的东西更有价值。在这里,答案包含质量和重要信息。 (2认同)

Esk*_*ola 151

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

ThreadLocals是一种全局变量(尽管它们仅限于一个线程,因为它们仅限于一个线程),所以在使用它们时应该小心,以避免不必要的副作用和内存泄漏.设计您的API,以便在不再需要ThreadLocal值时将始终自动清除它们,并且无法正确使用API​​(例如,像这样).ThreadLocals可以用来使代码更清洁,在某些罕见的情况下,他们是做什么工作的唯一方法(我当前的项目有两个这样的情况下,它们都记录在这里下的"静态字段和全局变量").

  • 为什么我必须向下滚动才能找到这个必要的答案!? (25认同)
  • 这正是exPOJO框架(www.expojo.com)允许访问ORM Session/PersistenceManager而不需要注释和注入的开销.它有点像'线程注入'而不是'对象注入'.它提供对依赖项的访问,而不需要将它们嵌入到可能需要这些依赖项的每个对象中.当您使用线程注入代替经典DI(例如,Spring等)时,您可以制作DI框架的"轻量级"是多么令人惊讶 (3认同)
  • @JeremyStein,更好的答案位于/sf/answers/57253801/和/sf/answers/1217883691/和/sf/answers/57285511/ (2认同)

use*_*464 47

在Java中,如果你有一个可以根据每个线程变化的数据,你的选择是将该数据传递给需要(或可能需要)它的每个方法,或者将数据与线程相关联.如果所有方法都需要传递一个共同的"上下文"变量,那么在任何地方传递数据都是可行的.

如果不是这种情况,您可能不希望使用其他参数来混淆方法签名.在非线程世界中,您可以使用Java等效的全局变量来解决问题.在一个线程词中,全局变量的等价物是线程局部变量.

  • 因此,您应该以避免全局变量的方式避免线程局部化.我不可能接受创建全局变量(线程本地)而不是传递值,人们不喜欢,因为它经常揭示他们不想修复的架构问题. (10认同)
  • 也许......但是,如果你有一个庞大的现有代码库,你必须添加一个必须在_everywhere_周围传递的新数据,例如会话上下文,db事务,登录用户等,这可能很有用. . (8认同)
  • 感谢使用数据而不是数据. (3认同)

小智 19

" Java Concurrency in Practice"一书中有很好的例子.作者(Joshua Bloch)解释了线程限制是如何实现线程安全的最简单方法之一,而ThreadLocal是维护线程限制的更正式方法.最后,他还解释了人们如何通过将其作为全局变量来滥用它.

我已经复制了上述书中的文本,但缺少代码3.10,因为理解ThreadLocal的使用位置并不重要.

线程局部变量通常用于防止基于可变单元或全局变量的设计中的共享.例如,单线程应用程序可能会维护在启动时初始化的全局数据库连接,以避免必须将Connection传递给每个方法.由于JDBC连接可能不是线程安全的,因此使用全局连接而无需额外协调的多线程应用程序也不是线程安全的.通过使用ThreadLocal存储JDBC连接,如代码清单3.10中的ConnectionHolder,每个线程都有自己的连接.

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

通过将其线程限制属性视为使用全局变量的许可或作为创建"隐藏"方法参数的方法,很容易滥用ThreadLocal.与全局变量一样,线程局部变量可能会降低可重用性并在类之间引入隐藏的耦合,因此应谨慎使用.


Nei*_*fey 15

本质上,当您需要变量的值来依赖当前线程时,您不方便以其他方式将值附加到线程(例如,子类化线程).

一个典型的情况是,其他一些框架已经创建了运行代码的线程,例如servlet容器,或者使用ThreadLocal更有意义,因为你的变量是"在其逻辑位置"(而不是变量)挂起一个Thread子类或一些其他哈希映射).

在我的网站上,我有一些关于何时使用ThreadLocal的进一步讨论和示例,这些也可能是您感兴趣的.

有些人提倡使用ThreadLocal作为在需要线程编号的某些并发算法中将"线程ID"附加到每个线程的方法(参见例如Herlihy&Shavit).在这种情况下,请检查您是否真正获益!


Abh*_*wad 10

  1. Java中的ThreadLocal已经在JDK 1.2中引入,但后来在JDK 1.5中进行了泛化,以在ThreadLocal变量上引入类型安全性.

  2. ThreadLocal可以与Thread作用域相关联,Thread执行的所有代码都可以访问ThreadLocal变量,但是两个线程无法看到彼此的ThreadLocal变量.

  3. 每个线程都拥有一个ThreadLocal变量的独占副本,该变量在线程完成或死亡后通常或由于任何异常而有资格进入垃圾收集,因为这些ThreadLocal变量没有任何其他实时引用.

  4. Java中的ThreadLocal变量通常是Classes中的私有静态字段,并在Thread内维护其状态.

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


Ric*_*dle 9

文档说得非常好:"访问[线程局部变量]的每个线程(通过其get或set方法)都有自己独立初始化的变量副本".

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

  • 默认情况下,共享静态对象或在两个线程都拥有对同一对象的引用的线程之间显式传递的对象.在线程中本地声明的对象不会被共享(它们是线程堆栈的本地对象).只想澄清一下. (18认同)
  • 如果每个线程都想拥有自己的东西副本,为什么不能简单地将它声明为local(这总是线程安全的)? (4认同)

znl*_*lyj 9

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


小智 7

可以使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这里使用lambda表达式实现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()));
    } 

}
Run Code Online (Sandbox Code Playgroud)


And*_*mov 6

从Java 8版本开始,有更多的声明式初始化方式ThreadLocal

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");
Run Code Online (Sandbox Code Playgroud)

在Java 8发布之前,您必须执行以下操作:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};
Run Code Online (Sandbox Code Playgroud)

此外,如果用于的类的实例化方法(构造函数,工厂方法)ThreadLocal不带任何参数,则可以简单地使用方法引用(在Java 8中引入):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);
Run Code Online (Sandbox Code Playgroud)

注意: 评估是惰性的,因为您传递的java.util.function.Supplierlambda仅在ThreadLocal#get被调用时才评估,但先前未评估过值。


Jef*_*ley 5

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

当设置信息的代码第二次或第三次运行时,可能会发生不好的事情,因为您的线程上的信息可能会在您出乎意料的时候开始发生变化。因此,在再次设置之前,请注意确保 ThreadLocal 信息尚未设置。

  • 如果代码准备好处理它,重入不是问题。进入时,记下变量是否已经设置,退出时,恢复其先前的值(如果有),或删除它(如果没有)。 (2认同)

Kan*_*mar 5

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

这是通过为每个尝试访问它的线程提供可变对象的新实例来实现的。所以它是每个线程的本地副本。这是在方法中使实例变量像局部变量一样被访问的一些技巧。如您所知,方法局部变量仅对线程可用,一个区别是;一旦方法执行结束,方法局部变量将无法用于线程,因为与 threadlocal 共享的可变对象将在多个方法中可用,直到我们清理它。

按定义:

Java 中的 ThreadLocal 类使您能够创建只能由同一线程读取和写入的变量。因此,即使两个线程正在执行相同的代码,并且该代码引用了一个 ThreadLocal 变量,那么两个线程也无法看到彼此的 ThreadLocal 变量。

Threadjava中的每一个都包含ThreadLocalMap在其中。
在哪里

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.
Run Code Online (Sandbox Code Playgroud)

实现ThreadLocal:

现在为 ThreadLocal 创建一个包装类,它将保存如下所示的可变对象(有或没有initialValue())。
现在这个包装器的 getter 和 setter 将在线程本地实例而不是可变对象上工作。

如果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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在wrapper.getDateFormatter()将调用threadlocal.get()并检查currentThread.threadLocalMap包含(线程本地)实例。
如果是,则返回相应线程本地实例的值 (SimpleDateFormat),
否则添加具有此线程本地实例的映射,initialValue()。

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

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


小智 5

什么时候?

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

如何 ?

一个例子是 Spring 框架大量使用 ThreadLocal 来管理后台事务,方法是将这些连接对象保存在 ThreadLocal 变量中。在高层次上,当事务开始时,它获取连接(并禁用自动提交)并将其保存在 ThreadLocal 中。在进一步的 db 调用中,它使用相同的连接与 db 通信。最后,它从 ThreadLocal 获取连接并提交(或回滚)事务并释放连接。

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


Dim*_*mos 5

ThreadLocal 当您想要一些不应在不同线程之间共享的状态时,它很有用,但应该可以在每个线程的整个生命周期中访问它。

例如,想象一个 Web 应用程序,其中每个请求由不同的线程提供服务。想象一下,对于每个请求,您需要多次获取一条数据,这计算起来非常昂贵。但是,对于每个传入请求,该数据可能已更改,这意味着您不能使用普通缓存。这个问题的一个简单、快速的解决方案是使用一个ThreadLocal变量来保存对这些数据的访问,这样您只需为每个请求计算一次。当然,这个问题也可以不使用 来解决ThreadLocal,但我设计它是为了说明目的。

也就是说,请记住ThreadLocals 本质上是一种全局状态。因此,它具有许多其他含义,只有在考虑了所有其他可能的解决方案后才应使用。


moh*_*our 5

在多线程代码中使用像 SimpleDateFormat 这样的类助手有 3 种场景,最好的一种是使用ThreadLocal

场景

1-锁或同步机制的帮助下使用类似共享对象,这会使应用程序变慢

线程池场景

2-在方法中用作本地对象

在线程池中,在这种情况下,如果我们有4 个线程,每个线程1000 个任务时间,那么我们将创建
4000 个SimpleDateFormat对象并等待 GC 擦除它们

3-使用ThreadLocal

在线程池中,如果我们有 4 个线程,并且我们给每个线程一个 SimpleDateFormat 实例,
那么我们就有4 个线程4个 SimpleDateFormat对象

不需要锁机制和对象的创建和销毁。(良好的时间复杂度和空间复杂度)

https://www.youtube.com/watch?v=sjMe9aecW_A