关于java:何时以及如何使用ThreadLocal变量?

When and how should I use a ThreadLocal variable?

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

它是如何使用的?


一种可能的(也是常见的)用法是,当您有一些对象不是线程安全的,但您希望避免同步访问该对象(我在看您,simpledateformat)。相反,给每个线程一个它自己的对象实例。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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中数据的引用,因此在使用线程池的应用程序服务器中使用ThreadLocal时,可能会导致类加载泄漏。使用ThreadLocalThreadLocal方法清理任何ThreadLocalget()set()

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

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

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

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


使用多间maintain threadlocals to some context to the current线程相关。for example when the current is stored in a threadlocal交易,你不需要它的每一通电话as a parameter method,the needs一个堆栈在房屋下access to it.Web应用程序可能存储信息about the current session中的request和threadlocal,so that the application has easy access to them。你可以使用与导入时guice threadlocals for the custom注入的对象范围(S guice'默认servlet使用them as most probably阱范围)。P></

是一threadlocals sort of Global variables(因为他们是邪恶的,尽管slightly少限制线程,你知道to one should be)when using them to avoid小心泄密unwanted端效应和记忆。我知道你的API设计threadlocal that the values automatically when they will总是好的cleared are not needed放任了使用API和that of the possible(我不会这样for example)。threadlocals can be used to make the队列和清洁,在一些罕见的病例,他们是唯一的方式使工作电流的东西我有两例这样的项目在这里;他们是documented下静电场和全局变量")。P></


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

如果不是这样,您可能不想用一个额外的参数来混乱方法签名。在非线程世界中,可以用Java变量的全局变量来解决问题。在线程字中,全局变量的等价物是线程局部变量。


在实际应用中,有一个很好的例子。作者(JoshuaBloch)解释了线程限制是实现线程安全的最简单方法之一,而线程局部是维护线程限制的更正式的方法。最后,他还解释了人们如何利用它作为全局变量。

我从上面提到的书中复制了文本,但是代码3.10丢失了,因为理解threadlocal应该在哪里使用并不重要。

Thread-local variables are often used to prevent sharing in designs based on mutable Singletons or global variables. For example, a single-threaded application might maintain a global database connection that is initialized at startup to avoid having to pass a Connection to every method. Since JDBC connections may not be thread-safe, a multithreaded application that uses a global connection without additional coordination is not thread-safe either. By using a ThreadLocal to store the JDBC connection, as in ConnectionHolder in Listing 3.10, each thread will have its own connection.

ThreadLocal is widely used in implementing application frameworks. For example, J2EE containers associate a transaction context with an executing thread for the duration of an EJB call. This is easily implemented using a static Thread-Local holding the transaction context: when framework code needs to determine what transaction is currently running, it fetches the transaction context from this ThreadLocal. This is convenient in that it reduces the need to pass execution context information into every method, but couples any code that uses this mechanism to the framework.

It is easy to abuse ThreadLocal by treating its thread confinement property as a license to use global variables or as a means of creating"hidden" method arguments. Like global variables, thread-local variables can detract from reusability and introduce hidden couplings among classes, and should therefore be used with care.


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

典型的情况是,其他一些框架创建了代码运行的线程,例如servlet容器,或者使用threadlocal更合理,因为您的变量"在其逻辑位置"(而不是挂在线程子类或其他哈希图中的变量)。

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

有些人主张在某些需要线程编号的并发算法中,使用threadlocal将"线程ID"附加到每个线程(参见herlihy&shavit)。在这种情况下,检查你是否真的得到了好处!


线程池的服务器程序可以继续,ThreadLocalshould be removed VaR和响应客户端thus to the before,流线程模式下可reused request。P></


文档很好地说明了这一点:"每个访问[一个线程局部变量]的线程(通过其get或set方法)都有自己独立初始化的变量副本"。

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


  • Java中的TyLead本地已在JDK 1.2上引入,但后来在JDK 1.5中被推广,以在TyLead本地变量上引入类型安全性。

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

  • 每个线程都拥有一个线程局部变量的独占副本,该变量在线程完成或终止后(通常或由于任何异常)有资格进行垃圾收集,因为这些线程局部变量没有任何其他活动引用。

  • Java中的线程局部变量通常是类中的私有静态字段,并在线程内保持其状态。

  • 多读:Java中的线程局部-示例程序和教程


    两个可以使用threadLocal变量的用例-
    1-当我们需要将状态与线程关联时(例如,用户ID或事务ID)。这通常发生在Web应用程序中,每个发送到servlet的请求都有一个与之相关联的唯一TransactionID。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 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不是线程安全的,所以我们必须提供使其线程安全的机制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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发布以来,有更多的声明性方式来初始化EDCOX1,0个:

    1
    ThreadLocal<Cipher> local = ThreadLocal.withInitial(() ->"init value");

    在Java 8发布之前,您必须执行以下操作:

    1
    2
    3
    4
    5
    6
    ThreadLocal<String> local = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return"init value";
        }
    };

    此外,如果用于EDCOX1〔0〕的类的实例化方法(构造函数、工厂方法)不采取任何参数,则可以简单地使用方法引用(在Java 8中引入):

    1
    2
    3
    4
    5
    6
    class NotThreadSafe {
        // no parameters
        public NotThreadSafe(){}
    }

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

    注:由于您传递的是java.util.function.Supplierlambda,而该lambda仅在调用ThreadLocal#get时才进行计算,但之前没有对值进行计算,因此计算比较慢。


    什么时候?

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

    怎样?

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

    我认为log4j还使用threadlocal来维护MDC。


    ThreadLocal是有用的,当您希望有一些状态不应该在不同的线程之间共享,但它应该在整个生命周期内从每个线程访问。

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

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


    You have to be with the threadlocal模式非常小心。there are some菲尔唐类专业上述双方,但正是那一个让上述措施后,队列,集合threadlocal茶茶王"entrant context不~。"P></

    坏的事情可以发生,当队列的信息集合在运行时得到的二或三,因为信息可以在线改变你的线程,当你开始不期望它。亲爱的让我带不确定信息threadlocal t been the之前你又集集。P></


    ThreadLocal will ensure accessing the mutable object by the multiple
    threads in the non synchronized method is synchronized, means making
    the mutable object to be immutable within the method. This
    is achieved by giving new instance of mutable object for each thread
    try accessing it. So It is local copy to the each thread. This is some
    hack on making instance variable in a method to be accessed like a
    local variable. As you aware method local variable is only available
    to the thread, one difference is; method local variables will not
    available to the thread once method execution is over where as mutable
    object shared with threadlocal will be available across multiple
    methods till we clean it up.

    根据定义:

    The ThreadLocal class in Java enables you to create variables that can
    only be read and written by the same thread. Thus, even if two threads
    are executing the same code, and the code has a reference to a
    ThreadLocal variable, then the two threads cannot see each other's
    ThreadLocal variables.

    Java中的每一个EDCOX1 0都包含EDCOX1,1,其中的EDCOX1。在哪里?

    1
    2
    Key = One ThreadLocal object shared across threads.
    value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

    实现螺纹局部:

    现在为threadLocal创建一个包装类,它将保存下面这样的可变对象(有或没有initialValue())。现在,这个包装器的getter和setter将处理threadlocal实例,而不是可变对象。

    如果threadLocal的getter()在Thread的threadLocalMap中找不到任何值;那么它将调用initialValue()以获取与线程相关的私有副本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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


    没有什么真的在这里,但discovered that is useful今日甚ThreadLocalwhen using豆在Web应用中确认。确认消息是定,但Locale.getDefault()模式默认使用。你可以用不同的MessageInterpolatorValidatorconfigure the,but to the specify没有办法Localevalidate当你呼叫。我知道你可以创建静态(或更好ThreadLocalyet with other things通用集装箱,You might have need to be ThreadLocal然后你Localefrom that the custom MessageInterpolator回升。下一步在ServletFilterwhich is to write a session值uses本地拾取或request.getLocale()to the store ThreadLocal和恩在你的参考。P></


    上述模式是"unknown as(谷歌),它定义在全局变量usage is to which can be the value中的每个线程在referenced独特。这是usages typically some sort of contextual信息储存entails that is to the current of联执行线程。P></

    我们使用它在Java EE环境中,用户的身份证到Java EE(are not have access to *感知sessioncontext HttpSession,or the EJB)。* the usage of which makes队列,基于身份的安全接入CAN for the标识操作,从没有到过explicitly通*,/在每个呼叫的方法。P></

    the request of most /响应循环操作在Java EE makes this type of usage)在容易因为恩给出发点进入和退出unset to the threadlocal集和。P></


    线程局部变量通常用于防止在基于可变的单子或全局变量。

    它可以用于在不使用连接池时为每个线程建立单独的JDBC连接等场景。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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的用法。


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


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

    多读


    threadlocal是由JVM专门提供的一种功能,它只为线程提供独立的存储空间。与实例范围变量的值类似,变量仅绑定到类的给定实例。每个对象都有其唯一的值,它们看不到彼此的值。线程局部变量的概念也是如此,从对象实例的意义上来说,它们是线程的局部变量,除了创建它的线程外,其他线程看不到它。看到这里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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的类助手有三种情况,其中最好使用threadlocal

    情节

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

    2-用作方法内的本地对象

    在这个场景中,如果我们有4个线程,每个线程调用一个方法1000次,那么我们有已创建4000 simpledateformat对象,正在等待GC清除它们

    3-使用ThreadLocal

    如果我们有4个线程,并且给每个线程一个simpledateformat实例所以我们有4个线程,4个simpledateformat对象。

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


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