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

它是如何使用的?


当前回答

在多线程代码中使用类似SimpleDateFormat的类助手有三种场景,其中最好的一种是使用ThreadLocal

场景

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

线程池操作场景

2-在方法中作为局部对象使用

在线程池中,在这个场景中,如果我们有4个线程每个线程有1000个任务时间,那么我们有 创建了4000 SimpleDateFormat对象,并等待GC删除它们

3-使用ThreadLocal

在线程池中,如果我们有4个线程,我们给每个线程一个SimpleDateFormat实例 我们有4个线程,4个SimpleDateFormat对象。

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

https://www.youtube.com/watch?v=sjMe9aecW_A

其他回答

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

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

我已经从提到的书中复制了文本,但代码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.

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

我遇到过这样一种情况,在每次更新通知时,多个线程都在创建可变缓存的映像。

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

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

在多线程代码中使用类似SimpleDateFormat的类助手有三种场景,其中最好的一种是使用ThreadLocal

场景

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

线程池操作场景

2-在方法中作为局部对象使用

在线程池中,在这个场景中,如果我们有4个线程每个线程有1000个任务时间,那么我们有 创建了4000 SimpleDateFormat对象,并等待GC删除它们

3-使用ThreadLocal

在线程池中,如果我们有4个线程,我们给每个线程一个SimpleDateFormat实例 我们有4个线程,4个SimpleDateFormat对象。

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

https://www.youtube.com/watch?v=sjMe9aecW_A

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

}