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

时间:2010-05-06 19:54:23

标签: java multithreading

我在这里读取ThreadDocal的JavaDoc

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ThreadLocal.html

它说 “ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户ID或事务ID)。”

但是我的问题是他们为什么选择将它设置为静态(通常) - 它使得“每个线程”状态变得有些混乱但字段是静态的?

7 个答案:

答案 0 :(得分:118)

因为如果它是一个实例级别字段,那么它实际上是“每个线程 - 每个实例”,而不仅仅是一个保证的“每个线程”。这通常不是您正在寻找的语义。

通常它会保存类似于用户对话,Web请求等对象的对象。您不希望它们也作为类的实例进行子作用。
一个网络请求=>一个持久性会议 没有一个网络请求=>每个对象一个持久性会话。

答案 1 :(得分:14)

要么使它静态,要么试图避免类中的任何静态字段 - 使类本身成为单例,然后只要全局可用该单例,就可以安全地使用实例级ThreadLocal。 p>

答案 2 :(得分:10)

不一定是这样。重要的是它应该是一个单身人士。

答案 3 :(得分:3)

原因是通过与线程关联的指针访问变量。它们就像具有线程范围的全局变量一样,因此静态是最接近的。这就是你在pthreads之类的东西中获得线程局部状态的方式,所以这可能只是历史和实现的意外。

答案 4 :(得分:1)

请参阅this,这可以使您更好地理解。

简而言之,ThreadLocal对象的工作方式类似于键值映射。当线程调用ThreadLocal get/set方法时,它将在地图的键中检索/存储线程对象,并在地图的值中检索/存储该值。这就是为什么不同的线程具有不同的值复制(要存储在本地)的原因,因为它驻留在不同的映射条目中。

这就是为什么您只需要一个映射即可保留所有值的原因。虽然不是必需的,但是您也可以有多个映射(不声明静态)来保留每个线程对象,这完全是多余的,这就是为什么首选静态变量的原因。

答案 5 :(得分:0)

在每个线程的每个实例上使用threadlocal是指如果您希望某个对象的所有方法中都可以看到某些内容并使其保持线程安全,而不像对普通字段那样同步访问它。

答案 6 :(得分:0)

static final ThreadLocal变量是线程安全的。

static使ThreadLocal变量仅可在各个线程的多个类中使用。这是跨多个类的各个线程局部变量的全局变量描述。

我们可以使用以下代码示例检查此线程的安全性。

  • CurrentUser-将当前用户ID存储在ThreadLocal中
  • TestService-具有方法的简单服务-getUser(),用于从CurrentUser中获取当前用户。
  • TestThread-此类用于创建多个线程并同时设置用户ID

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());
  });

}

}

运行TestThread主类。 输出-

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
...

分析摘要

  1. “主”线程启动,并将当前用户设置为“ 62”,并行启动“ ForkJoinPool.commonPool-worker-2”线程,并行启动“ ForkJoinPool.commonPool-worker-3”线程并将当前用户设置为“ 81”,并行启动“ ForkJoinPool.commonPool-worker-1”线程,并将当前用户设置为“ 87” 开始主菜单-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. 以上所有这些线程将休眠3秒
  3. main执行结束并显示当前用户为“ 62”,并行ForkJoinPool.commonPool-worker-1执行结束并显示当前用户为“ 87”,并行ForkJoinPool.commonPool-worker-2执行结束并显示当前用户用户为“ 31”, 并行ForkJoinPool.commonPool-worker-3执行结束,并将当前用户打印为“ 81”

推断

并发线程即使已声明为“静态最终ThreadLocal”,也能够检索正确的用户ID。