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

它是如何使用的?


当前回答

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

我们在Java EE环境中使用它来将用户标识传递给不支持Java EE的类(不能访问HttpSession或EJB SessionContext)。通过这种方式,代码可以从任何地方访问标识,而不必在每个方法调用中显式地传递它。

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

其他回答

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

我们在Java EE环境中使用它来将用户标识传递给不支持Java EE的类(不能访问HttpSession或EJB SessionContext)。通过这种方式,代码可以从任何地方访问标识,而不必在每个方法调用中显式地传递它。

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

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.

通过定义:

Java中的ThreadLocal类允许您创建这样的变量 只能在同一线程上读写。这样,即使是两个线程 正在执行相同的代码,并且该代码有一个对 变量ThreadLocal,那么两个线程不能看到彼此的线程 ThreadLocal变量。

java中的每个线程都包含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中没有找到任何值;然后它将调用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)实例。 如果是,返回对应threadlocal实例的值(SimpleDateFormat) 否则使用这个threadlocal实例initialValue()添加映射。

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

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

自Java 8发布以来,有更多的声明性方法来初始化ThreadLocal:

ThreadLocal<String> 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.Supplier lambda只在调用ThreadLocal#get但value之前没有计算时才会计算。

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

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

在我的网站上,我有一些关于何时使用ThreadLocal的进一步讨论和示例,您可能也会感兴趣。

有些人主张使用ThreadLocal作为一种方法,在某些并发算法中,当你需要线程号时,将“线程ID”附加到每个线程上(参见Herlihy & Shavit)。在这种情况下,检查你是否真的得到了好处!

试试这个小例子,感受一下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'。