什么时候以及如何使用ThreadLocal变量?


874

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

如何使用?


11
如果您使用的是ThreadLocal,请不要在上面编写包装器!每个想要使用该变量的开发人员都必须知道它是“ ThreadLocal”
不是错误

2
@Notabug如果您是显式的,则可以命名变量,以便您正在处理ThreadLocal值,例如RequestUtils.getCurrentRequestThreadLocal()。虽然并不是说这很优雅,但这是由于ThreadLocal本身在大多数情况下不是很优雅。
whirlwin

@Sasha可以使用ThreadLocal来存储执行跟踪
gaurav

Answers:


863

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

文件资料


160
同步或threadlocal的另一种替代方法是使变量成为局部变量。本地变量始终是线程安全的。我认为将DateFormats设置为本地是一种不好的做法,因为创建起来很昂贵,但是我从未见过关于此主题的可靠指标。
朱利安·查斯唐

18
这是黑客付出的高昂代价SimpleDateFormat。也许最好使用线程安全的替代方法。如果您同意单身人士不好,那就ThreadLocal更糟了。
亚历山大·里佐夫

3
@overthink是否有任何理由将ThreadLocal声明为static和final,我的意思是性能?
Sachin Gorade

4
ThreadLocal.get()方法将为每个线程调用ThreadLocal.initialValue()(一次),这意味着将为每个线程创建一个SimpleDateFormat对象。将SimpleDateFormat用作局部变量不是更好的方法(因为我们不必处理垃圾收集问题)吗?
sudeepdino008

3
请注意不要对您的SimpleDateFormat使用双括号初始化,因为这会创建一个匿名类,因此无法对您的类加载器进行垃圾回收。内存泄漏:返回新的SimpleDateFormat(){{applyPattern(“ yyyyMMdd HHmm”)}};
user1944408 '16

426

由于a ThreadLocal是对给定数据的引用,因此在使用线程池的应用程序服务器中使用s Thread时,可能会导致类加载泄漏ThreadLocal。您需要非常小心,以清理ThreadLocal您的所有内容get()set()使用ThreadLocalremove()方法。

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

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

如果您确实遇到了这些问题,则可以使用Eclipse的Memory Analyzer和/或遵循Frank Kieviet的指南后续步骤,确定哪个线程和类保留了这些引用。

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


11
Alex Vasseur移动了他的博客。是内存泄漏文章的当前链接。
肯斯特,2012年

12
Julien链接到的线程似乎已经移到此处,并且值得一读……
Donal Fellows 2013年

28
对于这个答案,虽然提供了很多信息,但它实际上并没有以任何方式回答这个问题,这是很多批评。
罗宾

7
现在PermGen被Java 8杀死了,这是否会以任何方式改变这个答案?
邪恶的洗衣机

12
@罗宾:我不同意。问题在于如何正确使用ThreadLocal,以全面理解任何概念(此处为ThreadLocal),理解如何不使用它以及不慎使用它的风险也很重要,这就是Phil的答案。我很高兴他涵盖了这一点,而其他任何答案都没有涵盖。所有这些票是当之无愧的。我相信SO应该建立对概念的理解,而不仅仅是成为质量检查站点。
Saurabh Patil

166

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

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


4
这正是exPOJO框架(www.expojo.com)允许访问ORM Session / PersistenceManager的方式,而无需注释和注入的开销。这有点像“线程注入”而不是“对象注入”。它提供了对依赖项的访问,而无需(和开销)将它们嵌入可能需要这些依赖项的每个对象中。令人惊讶的是,当您使用线程注入而不是经典的DI(例如Spring等)时,如何轻巧地构建DI框架
Volksman 2013年

27
为什么我必须向下滚动才能找到这个基本答案!?
杰里米·斯坦

1
@Esko,在您的项目中,不需要在哈希码和equals中使用ThreadLocal。更好的方法是在需要时创建或传递对哈希码/等于函数的引用。这样,您可以完全避免使用ThreadLocal hack。
Pacerier's


1
这是使用TL的最好解释。
德克斯特(Dexter)

51

在Java中,如果您的数据可以随每个线程而变化,则您的选择是将该数据传递给需要(或可能需要)该数据的每个方法,或者将该数据与线程相关联。如果您的所有方法都已经需要传递一个公共的“ context”变量,那么将数据传递到任何地方都是可行的。

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


13
因此,您应像避免全局变量一样避免线程局部变量。我可能无法接受创建全局变量(线程局部变量)而不是传递值的方法,人们不喜欢,因为它经常会显示出他们不想修复的体系结构问题。
Juan Mendes 2013年

10
也许...但是,如果您有一个庞大的现有代码库,而您必须在其中添加必须在任何地方传递的新数据(例如,会话上下文,数据库事务,登录用户等),这可能会很有用。。
山茱萸马森

5
使用基准代替数据的荣誉。

23

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

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

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

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

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


18

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

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

在我的网站上,我还进行了一些进一步的讨论,并举例说明了何时使用ThreadLocal可能也会引起您的兴趣。

有人主张使用ThreadLocal作为在某些需要线程号的并发算法中将“线程ID”附加到每个线程的方法(例如,参见Herlihy&Shavit)。在这种情况下,请检查您是否真的受益匪浅!


可以使用ThreadLocal来存储执行跟踪

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

  2. ThreadLocal可以与Thread范围关联,由Thread执行的所有代码都可以访问ThreadLocal变量,但是两个线程彼此看不到ThreadLocal变量。

  3. 每个线程都拥有ThreadLocal变量的互斥副本,在正常情况下或由于任何异常而导致线程结束或死亡后,该线程才有资格进行垃圾回收,因为这些ThreadLocal变量没有任何其他实时引用。

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

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


可以使用ThreadLocal来存储执行跟踪

11

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

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


18
默认情况下,共享静态对象或在两个线程都引用同一对象的线程之间显式传递的对象。在线程中本地声明的对象不共享(它们在线程堆栈中是本地的)。只是想澄清一下。
伊恩·瓦利

@IanVarley:感谢您指出这一点。因此,如果我们在MyThread类中声明并使用了变量,那么在这种情况下是否需要ThreadLocal?示例:类MyThread扩展了Thread {String var1 ;, int var2; 在这种情况下,var1和var2将成为线程自己的堆栈的一部分,并且不会共享,那么在这种情况下,我们是否需要ThreadLocal?我的理解可能是完全错误的,请指导。
Jayesh

4
如果每个线程都希望拥有自己的副本,为什么不能简单地将其声明为local(始终是线程安全的)?
sudeepdino008'6

@ sudeepdino008您并不总是可以控制那些线程(Webapp框架,线程

10

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


8

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

请注意,此处的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的原子整数私有静态最终AtomicInteger nextId = new AtomicInteger(0); //返回当前线程的唯一ID,并在必要时分配给它。public static int get(){return nextId..getAndIncrement(); }```
dashenswen

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被调用时才评估,但先前未评估过值。


5

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

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


2
如果代码准备好处理它,重入就不是问题。在输入时,请记下该变量是否已设置,在退出时,请恢复其先前值(如果有的话),或将其删除(如果没有)。
超级猫

1
@ Jeff,Dude,这对您编写的每个代码都是正确的,而不仅仅是ThreadLocal模式。如果您这样做F(){ member=random(); F2(); write(member); }并且F2用新值覆盖成员,则显然write(member)将不再写入您已random()编辑的号码。这实际上只是常识。同样,如果您这样做F(){ F(); },那么gd 会遇到无限循环!这在任何地方都是正确的,并非特定于ThreadLocal
Pacerier

@Jeff,代码示例?
Pacerier

5

什么时候?

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

怎么样 ?

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

我认为log4j还使用ThreadLocal维护MDC。


5

ThreadLocal 当您希望拥有某种状态时,这些状态不应该在不同线程之间共享,但是应该在每个线程的整个生命周期中都可以访问它,这很有用。

例如,假设有一个Web应用程序,其中每个请求由不同的线程处理。想象一下,对于每个请求,您都需要多次数据,这对于计算而言是非常昂贵的。但是,每个传入请求的数据可能都已更改,这意味着您不能使用普通缓存。一个简单,快速的解决方案是使用一个ThreadLocal变量来保存对该数据的访问权限,这样您就不必为每个请求计算一次。当然,也可以不使用来解决此问题ThreadLocal,但我出于说明目的设计了它。

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


除非您将ThreadLocals设为全局状态,否则它们不是全局状态。实际上,它们始终是可访问的堆栈资源。您可以通过简单地在所有方法中传递该变量(被延迟)来模拟本地线程。但这并不能使其成为全球性国家……
Enerccio

从某种意义上说,它是一种全局状态,可以从代码的任何位置(在同一线程的上下文中)访问。这带来了所有影响,例如无法推理谁可以读写此值。使用函数参数并不是一件容易的事,这是促进干净接口的良好实践。但是,我同意您的观点,即在代码库的整个深度中传递参数是一种代码味道。但是,我也认为,在许多情况下,首先使用ThreadLocal会导致代码臭味,因此应该重新考虑这一点。
Dimos

它可以只是对象状态的一部分,而不必全局使用。当然,您的开销很少,但是如果每个线程中多个对象必须具有不同的状态,则可以将其ThreadLocal用作对象字段...
Enerccio

你是对的。我可能偶然发现了的几种误用ThreadLocal,在全球范围内都可以使用它。如您所说,它仍然可以用作限制可见性的类字段。
Dimos

4

这里没有什么新鲜的东西,但是我今天发现,ThreadLocal在Web应用程序中使用Bean验证时,这非常有用。验证消息已本地化,但默认情况下使用Locale.getDefault()。您可以Validator用不同的配置MessageInterpolator,但是无法Locale在调用时指定validate。所以,你可以创建一个静态ThreadLocal<Locale>(或更好,但与其他事物一般的容器,你可能需要ThreadLocal再有您的自定义MessageInterpolatorLocale的这一点。下一步是写一个ServletFilter,它使用一个会话值或request.getLocale()挑区域和存储它供您ThreadLocal参考。


4

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

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

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


4

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实例上工作,而不是在可变对象上工作。

如果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();
    }
}

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

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

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


3

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

不使用连接池时,可以在为每个线程建立单独的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的使用。


2
在此示例中,一个主要问题是:谁负责关闭连接?不要将连接创建为初始值,而应让连接的使用者显式创建连接,然后通过ThreadLocal将其绑定到线程。然后,连接的创建者还负责关闭。在此示例中不清楚。创建和绑定连接也可以通过简单的框架(如Transaction.begin()和Transaction.end())隐藏起来。
瑞克·雷纳·路德维希

2

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

阅读更多


2

在多线程代码中有3种使用类辅助程序(如SimpleDateFormat)的方案,最好的一种是使用ThreadLocal

情境

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

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

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

3-使用ThreadLocal

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

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


2

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


1

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


1

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

}
}

1

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

我遇到的情况是,每个更新通知中都有多个线程正在创建可变缓存的映像。

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

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


0

尝试以下小示例,以了解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”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.