847 java concurrency multithreading thread-local thread-confinement
我ThreadLocal什么时候应该使用变量?
怎么用?
ove*_*ink 847
一种可能的(和常见的)用法是当你有一些不是线程安全的对象,但是你想避免同步对该对象的访问(我正在看你,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);
    }
}
文档.
Phi*_*l M 414
由于a ThreadLocal是对给定数据的引用,因此在使用线程池的应用程序服务器中使用s Thread时,最终会导致类加载泄漏ThreadLocal.你必须非常小心清理任何ThreadLocal是你get()或set()使用ThreadLocal的remove()方法.
如果在完成后没有清理,那么它作为已部署的webapp的一部分加载的类所持有的任何引用都将保留在永久堆中,并且永远不会被垃圾收集.重新部署/取消部署webapp不会清除每个Thread对webapp类的引用,因为Thread它不是您的webapp所拥有的.每个连续的部署都将创建一个永远不会被垃圾收集的类的新实例.
由于java.lang.OutOfMemoryError: PermGen space和一些谷歌搜索之后可能会增加-XX:MaxPermSize而不是修复错误,你最终会因内存不足而异常.
如果您最终遇到这些问题,可以使用Eclipse的Memory Analyzer和/或遵循Frank Kieviet的指南和后续内容来确定哪个线程和类保留了这些引用.
更新:重新发现了Alex Vasseur的博客文章,帮助我找到了ThreadLocal我遇到的一些问题.
Esk*_*ola 151
许多框架使用ThreadLocals来维护与当前线程相关的一些上下文.例如,当当前事务存储在ThreadLocal中时,您不需要通过每个方法调用将其作为参数传递,以防有人在堆栈中需要访问它.Web应用程序可能会在ThreadLocal中存储有关当前请求和会话的信息,以便应用程序可以轻松访问它们.使用Guice,您可以在为注入的对象实现自定义作用域时使用ThreadLocals (Guice的默认servlet作用域也很可能也使用它们).
ThreadLocals是一种全局变量(尽管它们仅限于一个线程,因为它们仅限于一个线程),所以在使用它们时应该小心,以避免不必要的副作用和内存泄漏.设计您的API,以便在不再需要ThreadLocal值时将始终自动清除它们,并且无法正确使用API(例如,像这样).ThreadLocals可以用来使代码更清洁,在某些罕见的情况下,他们是做什么工作的唯一方法(我当前的项目有两个这样的情况下,它们都记录在这里下的"静态字段和全局变量").
use*_*464 47
在Java中,如果你有一个可以根据每个线程变化的数据,你的选择是将该数据传递给需要(或可能需要)它的每个方法,或者将数据与线程相关联.如果所有方法都需要传递一个共同的"上下文"变量,那么在任何地方传递数据都是可行的.
如果不是这种情况,您可能不希望使用其他参数来混淆方法签名.在非线程世界中,您可以使用Java等效的全局变量来解决问题.在一个线程词中,全局变量的等价物是线程局部变量.
小智 19
" Java Concurrency in Practice"一书中有很好的例子.作者(Joshua Bloch)解释了线程限制是如何实现线程安全的最简单方法之一,而ThreadLocal是维护线程限制的更正式方法.最后,他还解释了人们如何通过将其作为全局变量来滥用它.
我已经复制了上述书中的文本,但缺少代码3.10,因为理解ThreadLocal的使用位置并不重要.
线程局部变量通常用于防止基于可变单元或全局变量的设计中的共享.例如,单线程应用程序可能会维护在启动时初始化的全局数据库连接,以避免必须将Connection传递给每个方法.由于JDBC连接可能不是线程安全的,因此使用全局连接而无需额外协调的多线程应用程序也不是线程安全的.通过使用ThreadLocal存储JDBC连接,如代码清单3.10中的ConnectionHolder,每个线程都有自己的连接.
ThreadLocal广泛用于实现应用程序框架.例如,J2EE容器在EJB调用期间将事务上下文与执行线程相关联.这可以使用保存事务上下文的静态Thread-Local轻松实现:当框架代码需要确定当前正在运行的事务时,它从此ThreadLocal获取事务上下文.这很方便,因为它减少了将执行上下文信息传递到每个方法的需要,但将使用此机制的任何代码耦合到框架.
通过将其线程限制属性视为使用全局变量的许可或作为创建"隐藏"方法参数的方法,很容易滥用ThreadLocal.与全局变量一样,线程局部变量可能会降低可重用性并在类之间引入隐藏的耦合,因此应谨慎使用.
Nei*_*fey 15
本质上,当您需要变量的值来依赖当前线程时,您不方便以其他方式将值附加到线程(例如,子类化线程).
一个典型的情况是,其他一些框架已经创建了运行代码的线程,例如servlet容器,或者使用ThreadLocal更有意义,因为你的变量是"在其逻辑位置"(而不是变量)挂起一个Thread子类或一些其他哈希映射).
在我的网站上,我有一些关于何时使用ThreadLocal的进一步讨论和示例,这些也可能是您感兴趣的.
有些人提倡使用ThreadLocal作为在需要线程编号的某些并发算法中将"线程ID"附加到每个线程的方法(参见例如Herlihy&Shavit).在这种情况下,请检查您是否真正获益!
Abh*_*wad 10
Java中的ThreadLocal已经在JDK 1.2中引入,但后来在JDK 1.5中进行了泛化,以在ThreadLocal变量上引入类型安全性.
ThreadLocal可以与Thread作用域相关联,Thread执行的所有代码都可以访问ThreadLocal变量,但是两个线程无法看到彼此的ThreadLocal变量.
每个线程都拥有一个ThreadLocal变量的独占副本,该变量在线程完成或死亡后通常或由于任何异常而有资格进入垃圾收集,因为这些ThreadLocal变量没有任何其他实时引用.
Java中的ThreadLocal变量通常是Classes中的私有静态字段,并在Thread内维护其状态.
阅读更多:http://javarevisited.blogspot.com/2012/05/how-to-use-threadlocal-in-java-benefits.html#ixzz2XltgbHTK
小智 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()));
    } 
}
从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被调用时才评估,但先前未评估过值。
您必须非常小心使用 ThreadLocal 模式。Phil 提到了一些主要的缺点,但没有提到的一个是确保设置 ThreadLocal 上下文的代码不是“可重入的”。
当设置信息的代码第二次或第三次运行时,可能会发生不好的事情,因为您的线程上的信息可能会在您出乎意料的时候开始发生变化。因此,在再次设置之前,请注意确保 ThreadLocal 信息尚未设置。
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的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包含此(线程本地)实例。
如果是,则返回相应线程本地实例的值 (SimpleDateFormat),
否则添加具有此线程本地实例的映射,initialValue()。
在这个可变类上实现了线程安全;每个线程都使用自己的可变实例,但使用相同的 ThreadLocal 实例。意味着所有线程将共享相同的 ThreadLocal 实例作为键,但不同的 SimpleDateFormat 实例作为值。
https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java
小智 5
什么时候?
当一个对象不是线程安全的时,与其同步会妨碍可伸缩性,不如给每个线程一个对象并保持它的线程范围,即 ThreadLocal。最常用但不是线程安全的对象之一是数据库连接和 JMSConnection。
如何 ?
一个例子是 Spring 框架大量使用 ThreadLocal 来管理后台事务,方法是将这些连接对象保存在 ThreadLocal 变量中。在高层次上,当事务开始时,它获取连接(并禁用自动提交)并将其保存在 ThreadLocal 中。在进一步的 db 调用中,它使用相同的连接与 db 通信。最后,它从 ThreadLocal 获取连接并提交(或回滚)事务并释放连接。
我认为 log4j 也使用 ThreadLocal 来维护 MDC。
ThreadLocal 当您想要一些不应在不同线程之间共享的状态时,它很有用,但应该可以在每个线程的整个生命周期中访问它。
例如,想象一个 Web 应用程序,其中每个请求由不同的线程提供服务。想象一下,对于每个请求,您需要多次获取一条数据,这计算起来非常昂贵。但是,对于每个传入请求,该数据可能已更改,这意味着您不能使用普通缓存。这个问题的一个简单、快速的解决方案是使用一个ThreadLocal变量来保存对这些数据的访问,这样您只需为每个请求计算一次。当然,这个问题也可以不使用 来解决ThreadLocal,但我设计它是为了说明目的。
也就是说,请记住ThreadLocals 本质上是一种全局状态。因此,它具有许多其他含义,只有在考虑了所有其他可能的解决方案后才应使用。
在多线程代码中使用像 SimpleDateFormat 这样的类助手有 3 种场景,最好的一种是使用ThreadLocal
场景
1-在锁或同步机制的帮助下使用类似共享对象,这会使应用程序变慢
线程池场景
2-在方法中用作本地对象
在线程池中,在这种情况下,如果我们有4 个线程,每个线程有1000 个任务时间,那么我们将创建
4000 个SimpleDateFormat对象并等待 GC 擦除它们
3-使用ThreadLocal
在线程池中,如果我们有 4 个线程,并且我们给每个线程一个 SimpleDateFormat 实例,
那么我们就有4 个线程,4个 SimpleDateFormat对象。
不需要锁机制和对象的创建和销毁。(良好的时间复杂度和空间复杂度)
https://www.youtube.com/watch?v=sjMe9aecW_A