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

时间:2009-05-03 19:59:21

标签: java multithreading concurrency thread-local thread-confinement

我应该何时使用ThreadLocal变量?

如何使用?

26 个答案:

答案 0 :(得分:827)

一种可能的(和常见的)用法是当你有一些非线程安全的对象,但你想避免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

答案 1 :(得分:403)

由于ThreadLocal是对给定Thread内数据的引用,因此在使用线程池的应用程序服务器中使用ThreadLocal时,最终可能会出现类加载泄漏。您需要非常谨慎地使用ThreadLocal的{​​{1}}方法清除get() set() ThreadLocalremove()

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

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

如果您最终遇到这些问题,可以使用Eclipse's Memory Analyzer和/或关注Frank Kieviet's guidefollowup来确定哪些主题和类保留了这些引用。

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

答案 2 :(得分:138)

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

ThreadLocals是一种全局变量(尽管它们仅限于一个线程,因为它们仅限于一个线程),所以在使用它们时应该小心,以避免不必要的副作用和内存泄漏。设计您的API,以便ThreadLocal值在不再需要时将始终自动清除,并且无法正确使用API​​(例如like this)。 ThreadLocals可用于使代码更清晰,在极少数情况下,它们是使某些东西工作的唯一方法(我当前的项目有两个这样的情况;它们在“静态字段和全局变量”下记录here

答案 3 :(得分:45)

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

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

答案 4 :(得分:15)

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

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

  

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

     

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

     

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

答案 5 :(得分:14)

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

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

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

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

答案 6 :(得分:9)

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

答案 7 :(得分:9)

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

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

答案 8 :(得分:8)

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

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

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

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

  5. 了解详情:ThreadLocal in Java - Example Program and Tutorial

答案 9 :(得分: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();
    }
}

请注意,这里使用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()));
    } 

}

答案 10 :(得分:6)

自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时进行评估,但之前未评估过值。

答案 11 :(得分:4)

当?

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

怎么样?

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

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

答案 12 :(得分:4)

当你想要一些不应该在不同线程之间共享的状态时,

ThreadLocal很有用,但它应该在每个线程的整个生命周期内都可以访问。

例如,假设一个Web应用程序,其中每个请求都由不同的线程提供。想象一下,对于每个请求,您需要多次执行一段数据,这非常昂贵。但是,每个传入请求的数据可能已更改,这意味着您无法使用普通缓存。解决此问题的一个简单,快速的解决方案是让ThreadLocal变量保持对此数据的访问权限,这样您只需为每个请求计算一次。当然,这个问题也可以在不使用ThreadLocal的情况下解决,但我将其设计用于说明目的。

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

答案 13 :(得分:4)

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

当设置信息的代码第二次或第三次运行时,可能会发生错误,因为您的线程上的信息可能会在您不期望它时开始变异。因此,请务必确保在再次设置之前未设置ThreadLocal信息。

答案 14 :(得分:3)

  

ThreadLocal将确保通过倍数访问可变对象   非同步方法中的线程是同步的,意味着制作   可变对象在方法中是不可变的。

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

按定义:

  

Java中的ThreadLocal类使您可以创建可以的变量   只能由同一个线程读取和写入。因此,即使是两个线程   正在执行相同的代码,并且代码具有对a的引用   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包含(threadlocal)实例。
如果是,则返回相应threadlocal实例的值(SimpleDateFormat) 否则使用此threadlocal实例initialValue()添加地图。

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

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

答案 15 :(得分:3)

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

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

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

答案 16 :(得分:3)

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

答案 17 :(得分:2)

线程局部变量通常用于防止基于的设计共享 可变的单身人士或全球变量。

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

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

答案 18 :(得分:1)

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

Read more

答案 19 :(得分:1)

[供参考] ThreadLocal无法解决共享库的更新问题。建议使用staticThreadLocal对象,该对象由同一线程中的所有操作共享。 [Mandatory] ​​remove()方法必须由ThreadLocal变量实现,尤其是在使用经常重用线程的线程池时。否则,它可能会影响后续的业务逻辑并导致意外的问题,例如内存泄漏。

答案 20 :(得分: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”。

答案 21 :(得分:0)

缓存,有时你需要计算相同的大量时间,因此通过将最后一组输入存储到方法中,结果可以加快代码速度。通过使用线程本地存储,您可以避免考虑锁定。

答案 22 :(得分:0)

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

}
}

答案 23 :(得分:0)

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

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

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

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

答案 24 :(得分:0)

在多线程代码中有3种使用Simplestrong类帮助程序的场景,例如SimpleDateFormat,最好的情况是使用 ThreadLocal

场景

1-通过锁定或同步机制像共享对象一样使用,这会使应用程序变慢

2-在方法内部用作本地对象

在这种情况下,如果我们每个有 4个线程,每个调用方法1000 ,那么我们有
4000 SimpleDateFormat 已创建对象,并等待GC删除它们

3-使用 ThreadLocal

如果我们有4个线程,并且给每个线程一个SimpleDateFormat实例
 因此我们有 4个线程 4个对象的SimpleDateFormat。

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

答案 25 :(得分:0)

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

  • LocaleContextHolder
  • TransactionContextHolder
  • RequestContextHolder
  • DateTimeContextHolder

第二个用例 - 当我们不想在线程之间共享某些东西,同时由于性能成本而不想使用同步/锁定时 示例 - 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