946

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

它是如何使用的?

4

26 回答 26

899

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

文档

于 2009-05-03T20:26:17.993 回答
446

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

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

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

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

更新:重新发现了Alex Vasseur 的博客条目,它帮助我找到了ThreadLocal我遇到的一些问题。

于 2009-05-03T22:02:09.963 回答
187

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

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

于 2009-05-04T13:36:12.137 回答
55

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

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

于 2009-05-03T20:20:39.023 回答
31

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

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

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

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

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

于 2015-10-04T09:28:35.137 回答
20

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

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

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

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

于 2009-05-03T23:50:05.463 回答
14
  1. Java 中的 ThreadLocal 已在 JDK 1.2 中引入,但后来在 JDK 1.5 中进行了泛化,以在 ThreadLocal 变量上引入类型安全。

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

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

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

阅读更多:Java 中的 ThreadLocal - 示例程序和教程

于 2013-07-01T06:10:23.777 回答
11

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

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

于 2009-05-03T20:05:21.697 回答
10

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

于 2013-04-28T15:22:29.383 回答
8

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

}
于 2015-08-20T15:58:15.827 回答
7

自 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.Supplierlambda 仅在被调用时才被评估,ThreadLocal#get但之前未评估值。

于 2018-03-27T09:24:05.200 回答
5

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

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

于 2012-10-23T18:50:09.197 回答
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.

实现 ThreadLocal:

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

Thread如果 threadlocal 的 getter() 在;的 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包含(线程本地)实例。
如果是,则返回相应线程本地实例的值 (SimpleDateFormat),
否则添加带有此线程本地实例的映射,initialValue()。

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

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

于 2015-12-02T12:07:44.933 回答
5

什么时候?

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

如何 ?

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

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

于 2016-04-27T03:10:49.330 回答
5

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

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

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

于 2017-09-16T11:28:11.223 回答
5

在多线程代码中使用像 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

于 2018-10-07T07:00:37.710 回答
4

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

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

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

于 2009-05-04T13:10:24.867 回答
4

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

于 2013-01-31T01:37:35.887 回答
4

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

它可以用于在不使用连接池时为每个线程建立单独的 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 值存储在线程对象本身中,一旦线程被垃圾回收,这些值也会消失。

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

于 2017-11-29T21:49:51.167 回答
3

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

于 2015-06-15T17:19:01.393 回答
2

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

}
}
于 2017-05-30T10:24:26.727 回答
2

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

阅读更多

于 2018-06-04T13:18:08.630 回答
2

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

于 2018-11-05T14:31:01.240 回答
1

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

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

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

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

于 2017-12-12T03:00:41.830 回答
1

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

于 2020-02-15T21:13:27.180 回答
0

第一个用例- 提供线程安全和性能的每个线程上下文 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的主要原因
于 2021-07-28T05:00:54.713 回答