ChatGPT解决这个技术问题 Extra ChatGPT

我应该何时以及如何使用 ThreadLocal 变量?

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

它是如何使用的?

如果您使用的是 ThreadLocal,请不要在其上编写包装器!!!每个想要使用该变量的开发人员都必须知道它是“ThreadLocal”
@Notabug 如果您是明确的,您可以命名您的变量,以便您处理 ThreadLocal 值,例如 RequestUtils.getCurrentRequestThreadLocal()。虽然不是说这很优雅,但这源于 ThreadLocal 本身在大多数情况下并不是很优雅的事实。

o
overthink

一种可能的(也是常见的)用途是当您有一些不是线程安全的对象时,但您想避免 synchronizing 访问该对象(我在看着你,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);
    }
}

Documentation


同步或线程本地的另一种替代方法是使变量成为局部变量。本地变量始终是线程安全的。我认为将 DateFormats 设为本地是不好的做法,因为它们的创建成本很高,但我从未见过关于这个主题的可靠指标。
这是黑客攻击 SimpleDateFormat 的高昂代价。也许使用 a thread-safe alternative 会更好。如果您同意 singletons are bad,那么 ThreadLocal 会更糟。
@overthink 有什么理由声明 ThreadLocal 静态和最终的,我的意思是性能还是什么?
ThreadLocal.get() 方法将为每个线程调用一次 ThreadLocal.initialValue(),这意味着为每个线程创建一个 SimpleDateFormat 对象。将 SimpleDateFormat 作为局部变量不是更好吗(因为我们不必处理垃圾收集问题)?
请注意不要为您的 SimpleDateFormat 使用双括号初始化,因为这会创建一个匿名类,因此您的类加载器不会被垃圾收集。内存泄漏:return new SimpleDateFormat(){{applyPattern("yyyyMMdd HHmm")}};
L
Laurel

由于 ThreadLocal 是对给定 Thread 中数据的引用,因此在使用线程池的应用程序服务器中使用 ThreadLocal 时,您最终可能会遇到类加载泄漏。您需要非常小心地使用 ThreadLocalremove() 方法清理任何 ThreadLocal 您的 get()set()

如果您在完成后不清理,则它对作为已部署 web 应用程序的一部分加载的类的任何引用都将保留在 permanent heap 中,并且永远不会被垃圾收集。重新部署/取消部署 web 应用不会清除每个 Thread 对您的 web 应用类的引用,因为 Thread 不属于您的 web 应用。每个连续的部署都会创建一个永远不会被垃圾收集的类的新实例。

由于java.lang.OutOfMemoryError: PermGen space,您最终会出现内存不足的异常,并且经过一些谷歌搜索后可能只会增加 -XX:MaxPermSize 而不是修复错误。

如果您最终确实遇到了这些问题,您可以使用 Eclipse's Memory Analyzer 和/或按照 Frank Kieviet's guidefollowup 确定保留这些引用的线程和类。

更新:重新发现了 Alex Vasseur's blog entry,它帮助我找到了我遇到的一些 ThreadLocal 问题。


Alex Vasseur 移动了他的博客。 Here 是内存泄漏文章的当前链接。
Julien 链接的主题似乎已移至 here,并且非常值得一读……
这是一个非常多的答案,虽然内容丰富,但实际上并没有回答这个问题。
现在 PermGen 被 Java 8 杀死了,这会以任何方式改变这个答案吗?
@Robin:我不同意。问题是关于如何正确使用 ThreadLocal,要全面了解任何概念(此处为 ThreadLocal),了解如何不使用它以及不小心使用它的风险也很重要,这就是 Phil 的答案。我很高兴他涵盖了任何其他答案都没有涵盖的这一点。所有这些选票都是当之无愧的。我相信 SO 应该建立对概念的理解,而不仅仅是一个 QA 站点。
L
Laurel

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

ThreadLocals 是一种全局变量(尽管由于它们被限制在一个线程中而稍微不那么邪恶),因此在使用它们时应该小心以避免不必要的副作用和内存泄漏。设计您的 API,以便 ThreadLocal 值在不再需要时始终自动清除,并且不会错误地使用 API(例如 like this)。 ThreadLocals 可用于使代码更清晰,并且在极少数情况下,它们是使某些工作正常工作的唯一方法(我当前的项目有两个这样的情况;它们记录在“静态字段和全局变量”下的 here)。


这正是 exPOJO 框架 (www.expojo.com) 允许访问 ORM Session/PersistenceManager 而无需注释和注入开销的方式。它有点像“线程注入”而不是“对象注入”。它提供了对依赖项的访问,而无需(和开销)将它们嵌入可能需要这些依赖项的每个对象中。当您使用线程注入而不是经典 DI(例如,Spring 等)时,您可以创建一个 DI 框架是多么“轻量级”,这真是令人惊讶
为什么我必须向下滚动才能找到这个重要的答案!?
@Esko,在您的项目中,不需要在 hashcode 和 equals 中使用 ThreadLocal。更好的方法是在需要时创建或传递对 hashcode/equals 函数的引用。这样,您可以完全避免 ThreadLocal hack。
这是对 TL 使用的最好解释。
u
user100464

在 Java 中,如果您有一个可以在每个线程中变化的数据,您的选择是将该数据传递给每个需要(或可能需要)它的方法,或者将该数据与线程相关联。如果您的所有方法都需要传递一个通用的“上下文”变量,那么到处传递数据可能是可行的。

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


所以你应该像避免全局变量一样避免线程局部变量。我不可能接受创建全局变量(线程局部变量)而不是传递值是可以的,人们不喜欢它,因为它经常揭示他们不想修复的架构问题。
也许......但是,如果您有一个巨大的现有代码库,您必须添加一个必须在各处传递的新数据,例如会话上下文、数据库事务、登录用户等,这可能会很有用.
使用数据而不是数据的荣誉。
这让我很好奇。 'datum' is the singular form and 'data' is the plural form.
C
Community

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

我已经从提到的书中复制了文本,但是缺少代码 3.10,因为了解应该在哪里使用 ThreadLocal 并不重要。

线程局部变量通常用于防止在基于可变单例或全局变量的设计中共享。例如,单线程应用程序可能会维护一个在启动时初始化的全局数据库连接,以避免必须将 Connection 传递给每个方法。由于 JDBC 连接可能不是线程安全的,因此在没有额外协调的情况下使用全局连接的多线程应用程序也不是线程安全的。通过使用 ThreadLocal 来存储 JDBC 连接,如清单 3.10 中的 ConnectionHolder,每个线程都有自己的连接。 ThreadLocal 广泛用于实现应用程序框架。例如,J2EE 容器在 EJB 调用期间将事务上下文与执行线程相关联。这很容易使用持有事务上下文的静态 Thread-Local 实现:当框架代码需要确定当前正在运行的事务时,它会从该 ThreadLocal 中获取事务上下文。这很方便,因为它减少了将执行上下文信息传递给每个方法的需要,但将使用此机制的任何代码耦合到框架。通过将其线程限制属性视为使用全局变量的许可或作为创建“隐藏”方法参数的一种手段,很容易滥用 ThreadLocal。与全局变量一样,线程局部变量会降低可重用性并在类之间引入隐藏的耦合,因此应谨慎使用。


L
Laurel

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

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

在我的网站上,我还有一些可能也很有趣的discussion and examples of when to use ThreadLocal

有些人提倡使用 ThreadLocal 作为在某些需要线程号的并发算法中将“线程 ID”附加到每个线程的一种方式(例如,参见 Herlihy 和 Shavit)。在这种情况下,请检查您是否真的得到了好处!


L
Laurel

Java 中的 ThreadLocal 已在 JDK 1.2 中引入,但后来在 JDK 1.5 中进行了泛化,以在 ThreadLocal 变量上引入类型安全。 ThreadLocal 可以与 Thread 作用域相关联,Thread 执行的所有代码都可以访问 ThreadLocal 变量,但两个线程不能看到彼此的 ThreadLocal 变量。每个线程都拥有一个 ThreadLocal 变量的独占副本,该副本在线程完成或死亡后(通常或由于任何异常)有资格进行垃圾收集,因为这些 ThreadLocal 变量没有任何其他实时引用。 Java 中的 ThreadLocal 变量通常是 Classes 中的私有静态字段,并在 Thread 内部维护其状态。

阅读更多:ThreadLocal in Java - Example Program and Tutorial


l
lakshman

The documentation 说得很好:“每个访问[线程局部变量](通过其 get 或 set 方法)的线程都有自己的、独立初始化的变量副本”。

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


默认情况下,静态对象或在两个线程都拥有对同一对象的引用的线程之间显式传递的对象是共享的。在线程中本地声明的对象不是共享的(它们是线程堆栈的本地对象)。只是想澄清一下。
@IanVarley:感谢您指出这一点。因此,如果我们有在 MyThread 类中声明和使用的变量,那么在这种情况下我们需要 ThreadLocal 吗?示例:class MyThread extends Thread{ String var1;, int var2;在这种情况下 var1 和 var2 将是 Thread 自己的堆栈的一部分并且不共享,那么在这种情况下我们需要 ThreadLocal 吗?我的理解可能完全错误,请指导。
如果每个线程都想拥有自己的副本,为什么不能简单地将其声明为本地的(这始终是线程安全的)?
@sudeepdino008 您并不总是可以控制这些线程的创建(webapp 框架、线程池库)
L
L Joey

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


B
Brad Larson

可以使用线程局部变量的两个用例 - 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()));
    } 

}

如果您的所有目的是返回一个增量的唯一整数值,我仍然没有看到使用 ThreadLocal 在示例一中生成唯一 ID 的好处。 ``` public class ThreadId { // 包含要分配的下一个线程 ID 的原子整数 private static final AtomicInteger nextId = new AtomicInteger(0); // 返回当前线程的唯一 ID,必要时分配 public static int get() { return nextId..getAndIncrement(); } } ```
A
Andrii Abramov

自 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 但之前未评估值时才评估。


J
Jeff Richley

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

当设置信息的代码第二次或第三次运行时,可能会发生不好的事情,因为线程上的信息可能会在您未预料到的情况下开始变异。所以在再次设置之前请注意确保没有设置 ThreadLocal 信息。


如果代码准备好处理它,那么重入不是问题。在进入时,记下变量是否已经设置,在退出时,恢复其先前的值(如果有的话),或者删除它(如果没有的话)。
@Jeff,伙计,您编写的每个代码都是如此,而不仅仅是 ThreadLocal 模式。如果您执行 F(){ member=random(); F2(); write(member); } 并且 F2 用新值覆盖成员,那么显然 write(member) 将不再写入您已 random() 编辑的数字。这实际上只是常识。同样,如果您执行 F(){ F(); },那么您的无限循环会很幸运!这在任何地方都是正确的,并不特定于 ThreadLocal
K
Kanagavelu Sugumar

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

按定义:

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

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 创建一个包装类,它将保存如下所示的可变对象(有或没有 initialValue())。
现在这个包装器的 getter 和 setter 将在 threadlocal 实例而不是可变对象上工作。

如果 threadlocal 的 getter() 在 Thread 的 threadlocalmap 中没有找到任何值;然后它将调用 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


L
Limestone

什么时候?

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

如何 ?

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

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


D
Dimos

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

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

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


ThreadLocals 不是全局状态,除非您将其设为全局状态。它们实际上总是可访问的堆栈资源。您可以通过在所有方法中简单地传递该变量来模拟线程本地(延迟);这不会使它成为全球状态...
它是全局状态的一种形式,从某种意义上说,它可以从代码中的任何位置(在同一线程的上下文中)访问。这会带来所有后果,例如无法推断谁读取和写入此值。使用函数参数并不是一件迟钝的事情,它是一种促进清洁界面的好习惯。但是,我同意您的观点,即在代码库的整个深度传递参数是一种代码异味。但是,我也相信,在很多情况下,使用 ThreadLocal 最初是一种代码味道,导致你来到这里,所以这个应该重新考虑。
它可以只是对象状态的一部分,不必在全局范围内使用。当然,你会得到一点点开销,但如果多个对象每个线程必须有不同的状态,你可以使用 ThreadLocal 作为对象字段......
你是对的。我可能偶然发现了 ThreadLocal 的几个误用,它可以在全球范围内访问。正如您所说,它仍然可以用作限制可见性的类字段。
m
mohsen.nour

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

场景

1-通过锁定或同步机制使用like share object,这使应用程序变慢

线程池场景

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

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

3- 使用 ThreadLocal

在线程池中,如果我们有 4 个线程,我们给每个线程一个 SimpleDateFormat 实例,所以我们有 4 个线程,4 个 SimpleDateFormat 对象。

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

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


C
Colselaw

这里没有什么新东西,但我今天发现 ThreadLocal 在 Web 应用程序中使用 Bean Validation 时非常有用。验证消息已本地化,但默认使用 Locale.getDefault()。您可以使用不同的 MessageInterpolator 配置 Validator,但在调用 validate 时无法指定 Locale。因此,您可以创建一个静态 ThreadLocal<Locale>(或者更好的是,一个包含您可能需要的其他东西的通用容器 ThreadLocal,然后让您的自定义 MessageInterpolator 从中选择 Locale。下一步是编写一个ServletFilter 使用会话值或 request.getLocale() 选择区域设置并将其存储在您的 ThreadLocal 参考中。


A
Arjan Tijms

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

我们在 Java EE 环境中使用它来将用户身份传递给不支持 Java EE 的类(无权访问 HttpSession 或 EJB SessionContext)。这样,将身份用于基于安全性的操作的代码可以从任何地方访问身份,而无需在每个方法调用中显式传递它。

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


b
bpjoshi

线程局部变量通常用于防止在基于可变单例或全局变量的设计中共享。

它可以用于在不使用连接池时为每个线程建立单独的 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 时,它将返回与该线程关联的连接。对于您不想在线程之间共享的其他属性(如日期格式、事务上下文)也可以这样做。

您也可以使用局部变量,但这些资源通常会在创建过程中占用时间,因此您不想在使用它们执行某些业务逻辑时一次又一次地创建它们。但是,ThreadLocal 值存储在线程对象本身中,一旦线程被垃圾回收,这些值也会消失。

这个 link 很好地解释了 ThreadLocal 的使用。


在这个例子中是一个主要问题:谁负责关闭连接?不要将此连接创建为初始值,而是让连接的使用者显式创建连接并通过 ThreadLocal 将其绑定到线程。连接的创建者也负责关闭。这在这个例子中并不清楚。创建和绑定连接也可以通过 Transaction.begin() 和 Transaction.end() 之类的东西隐藏在一个简单的框架中。
I
Ian Ringrose

缓存,有时您必须多次计算相同的值,因此通过将最后一组输入存储到方法和结果中,您可以加快代码速度。通过使用线程本地存储,您不必考虑锁定。


A
Ajay Kumar

ThreadLocal 是 JVM 专门提供的功能,仅为线程提供隔离的存储空间。就像实例范围变量的值一样,只绑定到类的给定实例。每个对象都有其唯一的值,它们无法看到彼此的值。 ThreadLocal 变量的概念也是如此,就对象实例而言,它们对于线程来说是本地的,除了创建它的线程之外,其他线程看不到它。 See Here

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

}
}

P
Pritesh Patel

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

Read more


Y
Young

[供参考]ThreadLocal不能解决共享对象的更新问题。建议使用一个 staticThreadLocal 对象,该对象由同一线程中的所有操作共享。 【强制】remove() 方法必须由 ThreadLocal 变量实现,尤其是使用线程池时,线程经常被复用。否则可能会影响后续业务逻辑,导致内存泄漏等意外问题。


D
Dev Amitabh

Threadlocal 提供了一种非常简单的方法来以零成本实现对象的可重用性。

我有一个情况,多个线程在每个更新通知上创建一个可变缓存的图像。

我在每个线程上使用了 Threadlocal,然后每个线程只需要重置旧图像,然后在每次更新通知时从缓存中再次更新它。

来自对象池的通常可重用对象具有与之相关的线程安全成本,而这种方法没有。


D
DigitShifter

试试这个小例子,感受一下 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'。


A
Arvind Kumar

第一个用例 - 提供线程安全和性能的每个线程上下文 SpringFramework 类中的实时示例 -

LocaleContextHolder

事务上下文持有者

请求上下文持有者

日期时间上下文持有者

第二个用例 - 当我们不想在线程之间共享某些东西并且同时由于性能成本而不想使用同步/锁定示例 - SimpleDateFormat 来创建日期的自定义格式

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author - GreenLearner(https://www.youtube.com/c/greenlearner)
 */
public class ThreadLocalDemo1 {
    SimpleDateFormat sdf = new SimpleDateFormat("dd-mm-yyyy");//not thread safe
    ThreadLocal<SimpleDateFormat> tdl1 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-dd-mm"));

    public static void main(String[] args) {
        ThreadLocalDemo1 d1 = new ThreadLocalDemo1();

        ExecutorService es = Executors.newFixedThreadPool(10);

        for(int i=0; i<100; i++) {
            es.submit(() -> System.out.println(d1.getDate(new Date())));
        }
        es.shutdown();
    }

    String getDate(Date date){

//        String s = tsdf.get().format(date);
        String s1 = tdl1.get().format(date);
        return s1;
    }
}

使用技巧

尽可能使用局部变量。这样我们就可以避免使用 ThreadLocal

尽可能将功能委托给框架

如果使用 ThreadLocal 并将状态设置为它,请确保在使用后对其进行清理,否则它可能成为 OutOfMemoryError 的主要原因