我什么时候应该使用 ThreadLocal 多变的?

它是如何使用的?

有帮助吗?

解决方案

一种可能的(和常见的)用法是当你有一些不是线程安全的对象,但你想避免同步访问该对象(我正在看着你, 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中数据的引用,因此在使用线程池在应用程序服务器中使用get()时,最终可能会出现类加载泄漏。您需要非常小心地使用set()remove()方法清理java.lang.OutOfMemoryError: PermGen space -XX:MaxPermSize <=>或<=> >。

如果您在完成后没有清理,那么它作为已部署的webapp的一部分加载的类所持有的任何引用都将保留在永久堆,永远不会收集垃圾。重新部署/取消部署Web应用程序不会清除每个<=>对您的webapp类的引用,因为<=>不属于您的webapp所拥有的内容。每个连续的部署都将创建一个永远不会被垃圾收集的类的新实例。

由于<=>会导致内存不足,并且在一些谷歌搜索之后可能会增加<=>而不是修复错误。

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

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

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

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

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

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

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

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

  

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

     

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

     

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

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

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

在我的网站上,我还有一些讨论以及何时使用ThreadLocal的示例也可能是有意义的。

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

文档说得很清楚:<!>;访问[线程局部变量]的每个线程(通过其get或set方法)都有自己的,独立初始化的变量<!> quot;。

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

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

  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. 了解详情: Java中的ThreadLocal - 示例程序和教程

可以使用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()));
    } 

}

自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";
    }
};

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

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

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

注意: 评估是懒惰的,因为您传递的是ThreadLocal#get lambda,仅在调用<=>时才评估,但之前未评估过值。

你必须非常小心ThreadLocal模式。 Phil提到了一些主要的缺点,但没有提到的是确保设置ThreadLocal上下文的代码不是<!> quot; re-entrant。<!>;

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

当?

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

怎么样?

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

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

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

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

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

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

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

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

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

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

  

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()在wrapper.getDateFormatter()的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();
    }
}

现在threadlocal.get()将调用currentThread.threadLocalMap,这将检查<=>包含(threadlocal)实例。
如果是,则返回相应threadlocal实例的值(SimpleDateFormat) 否则使用此threadlocal实例initialValue()添加地图。结果

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

https://github.com/skanagavelu/yt。高科技/斑点/主/ SRC / ThreadLocalTest.java

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

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

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

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

阅读更多内容

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

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

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

}
}

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

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

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

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

在多线程代码中使用类助手(如SimpleDateFormat)有3种情况,最好使用 ThreadLocal

方案

1 - 借助锁定或同步机制,使用共享对象,使应用缓慢

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

在这种情况下,如果我们有 4个帖子,每个调用方法1000 时间,那么我们有

4000 SimpleDateFormat 对象已创建并等待GC删除它们

3 - 使用 ThreadLocal

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top