简体   繁体   English

为什么 Java ThreadLocal 变量应该是静态的

[英]Why should Java ThreadLocal variables be static

I was reading the JavaDoc for Threadlocal here我在这里阅读了 Threadlocal 的 JavaDoc

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

and it says "ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (eg, a user ID or Transaction ID). "它说“ThreadLocal 实例通常是希望将状态与线程(例如,用户 ID 或事务 ID)相关联的类中的私有静态字段。”

But my question is why did they choose to make it static (typically) - it makes things a bit confusing to have "per thread" state but the fields are static?但我的问题是,为什么他们选择将其设为静态(通常)——“每线程”状态让事情有点混乱,但字段是静态的?

Because if it were an instance level field, then it would actually be "Per Thread - Per Instance", not just a guaranteed "Per Thread."因为如果它是一个实例级别的字段,那么它实际上是“每线程 - 每实例”,而不仅仅是一个有保证的“每线程”。 That isn't normally the semantic you're looking for.这通常不是您要寻找的语义。

Usually it's holding something like objects that are scoped to a User Conversation, Web Request, etc. You don't want them also sub-scoped to the instance of the class.通常它持有一些对象,这些对象的范围限定为用户对话、Web 请求等。您不希望它们也限定为类的实例。
One web request => one Persistence session.一个 Web 请求 => 一个 Persistence 会话。
Not one web request => one persistence session per object.不是一个 Web 请求 => 每个对象一个持久性会话。

要么将其设为静态,或者如果您试图避免类中的任何静态字段 - 将类本身设为单例,然后您就可以安全地使用实例级别 ThreadLocal,只要该单例在全局可用。

It doesn't have to be.不必如此。 The important thing is that it should be a singleton.重要的是它应该是一个单例。

The reason is that the variables are accessed via a pointer associated with the thread.原因是变量是通过与线程关联的指针访问的。 They act like global variables with thread scope, hence static is the closest fit.它们就像具有线程作用域的全局变量,因此静态是最合适的。 This is the way that you get thread local state in things like pthreads so this might just be an accident of history and implementation.这是您在 pthreads 之类的东西中获得线程本地状态的方式,所以这可能只是历史和实现的一个意外。

Refer to this , this give better understanding.参考这个,这可以更好地理解。

In short, ThreadLocal object work like a key-value map.简而言之, ThreadLocal对象就像一个键值映射。 When the thread invoke ThreadLocal get/set method, it will retrieve/store the thread object in the map's key, and the value in the map's value.当线程调用ThreadLocal get/set方法时,它将检索/存储映射键中的线程对象,以及映射值中的值。 That's why different threads has different copied of value (that you want to store locally), because it resides in different map's entry.这就是为什么不同的线程具有不同的值副本(您要在本地存储),因为它驻留在不同的地图条目中。

That's why you only need one map to keep all values.这就是为什么您只需要一张地图即可保留所有值的原因。 Although not necessary, you can have multiple map (without declare static) to keep each thread object as well, which, it is totally redundant, that's why static variable is preferred.尽管不是必需的,但您也可以使用多个映射(不声明静态)来保留每个线程对象,这完全是多余的,这就是首选静态变量的原因。

在每个线程的每个实例上使用 threadlocal 的一个用途是,如果您希望某些内容在对象的所有方法中可见并使其线程安全,而无需像对普通字段那样同步对它的访问。

static final ThreadLocal variables are thread safe. static final ThreadLocal变量是线程安全的。

static makes the ThreadLocal variable available across multiple classes for the respective thread only. static使 ThreadLocal 变量仅可用于各个线程的多个类。 it's a kind of Global variable decaration of the respective thread local variables across multiple classes.它是跨多个类的各个线程局部变量的一种全局变量声明。

We can check the this thread safety with the following code sample.我们可以使用以下代码示例检查此线程安全性。

  • CurrentUser - stores the current user id in ThreadLocal CurrentUser - 在 ThreadLocal 中存储当前用户 ID
  • TestService - Simple service with method - getUser() to fetch the current user from CurrentUser. TestService - 带有方法的简单服务 - getUser()从 CurrentUser 获取当前用户。
  • TestThread - this class used for creating multiple threads and set userids concurrently TestThread - 此类用于创建多个线程并同时设置用户标识

. .

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

. .

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

. .

Run TestThread main class.运行 TestThread 主类。 Output -输出 -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Analysis summary分析总结

  1. "main" thread starts and set current user as "62", parallely "ForkJoinPool.commonPool-worker-2" thread starts and set current user as "31", parallely "ForkJoinPool.commonPool-worker-3" thread starts and set current user as "81", parallely "ForkJoinPool.commonPool-worker-1" thread starts and set current user as "87" Start-main->62->62 Start-ForkJoinPool.commonPool-worker-2->31->31 Start-ForkJoinPool.commonPool-worker-3->81->81 Start-ForkJoinPool.commonPool-worker-1->87->87 “主”线程启动并将当前用户设置为“62”,并行“ForkJoinPool.commonPool-worker-2”线程启动并将当前用户设置为“31”,并行“ForkJoinPool.commonPool-worker-3”线程启动并设置当前用户为“81”,并行“ForkJoinPool.commonPool-worker-1”线程启动并将当前用户设置为“87” Start-main->62->62 Start-ForkJoinPool.commonPool-worker-2->31->31 Start-ForkJoinPool.commonPool-worker-3->81->81 Start-ForkJoinPool.commonPool-worker-1->87->87
  2. All these above threads will sleep for 3 seconds以上所有线程将休眠 3 秒
  3. main execution ends and print the current user as "62", parallely ForkJoinPool.commonPool-worker-1 execution ends and print the current user as "87", parallely ForkJoinPool.commonPool-worker-2 execution ends and print the current user as "31", parallely ForkJoinPool.commonPool-worker-3 execution ends and print the current user as "81" main执行结束并将当前用户打印为“62”,并行ForkJoinPool.commonPool-worker-1执行结束并将当前用户打印为“87”,并行ForkJoinPool.commonPool-worker-2执行结束并将当前用户打印为“ 31", 并行ForkJoinPool.commonPool-worker-3执行结束并将当前用户打印为 "81"

Inference推理

Concurrent Threads are able to retrieve correct userids even if it has declared as "static final ThreadLocal"并发线程能够检索正确的用户 ID,即使它已声明为“静态最终 ThreadLocal”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM