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

它是如何使用的?


当前回答

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

阅读更多

其他回答

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

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

由于ThreadLocal是对给定线程中的数据的引用,因此在使用线程池的应用服务器中使用ThreadLocal时,可能会导致类加载泄漏。在使用ThreadLocal的remove()方法清理你get()或set()的任何ThreadLocals时,你需要非常小心。

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

由于java.lang.OutOfMemoryError: PermGen空间,你最终会出现内存不足的异常,在谷歌搜索之后可能只是增加-XX:MaxPermSize,而不是修复这个错误。

如果您最终遇到了这些问题,您可以通过使用Eclipse的Memory Analyzer和/或遵循Frank Kieviet的指南和后续内容来确定哪个线程和类保留了这些引用。

更新:重新发现Alex Vasseur的博客条目,它帮助我找到了一些我遇到的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();
    }
}

注意,这里的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()));
    } 

}

第一个用例——每个线程上下文,提供线程安全性和性能 SpringFramework类中的实时示例-

LocaleContextHolder TransactionContextHolder RequestContextHolder DateTimeContextHolder

第二个用例——当我们不想在线程之间共享一些东西,同时由于性能成本而不想使用同步/锁定时 SimpleDateFormat为日期创建自定义格式

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author - GreenLearner(https://www.youtube.com/c/greenlearner)
 */
public class ThreadLocalDemo1 {
    SimpleDateFormat sdf = new SimpleDateFormat("dd-mm-yyyy");//not thread safe
    ThreadLocal<SimpleDateFormat> tdl1 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-dd-mm"));

    public static void main(String[] args) {
        ThreadLocalDemo1 d1 = new ThreadLocalDemo1();

        ExecutorService es = Executors.newFixedThreadPool(10);

        for(int i=0; i<100; i++) {
            es.submit(() -> System.out.println(d1.getDate(new Date())));
        }
        es.shutdown();
    }

    String getDate(Date date){

//        String s = tsdf.get().format(date);
        String s1 = tdl1.get().format(date);
        return s1;
    }
}

使用技巧

尽可能使用局部变量。这样我们就可以避免使用ThreadLocal 尽可能地将功能委托给框架 如果使用ThreadLocal并将状态设置在其中,请确保在使用后清理它,否则它可能成为OutOfMemoryError的主要原因

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

场景

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

线程池操作场景

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

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

3-使用ThreadLocal

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

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

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