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


101

我在这里阅读Threadlocal的JavaDoc

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

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

但是我的问题是,为什么他们选择使其静态化(通常)-使其具有“每个线程”状态,但字段是静态的则有些令人困惑?

Answers:


131

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

通常,它所保存的对象之类的对象仅限于用户对话,Web请求等。您不希望它们也被细分为类的实例。
一个Web请求=>一个Persistence会话。
没有一个Web请求=>每个对象一个持久性会话。


2
我喜欢这个解释,因为它显示了如何使用ThreadLocal
kellyfj 2010年

4
每个实例每个线程可能是一个有用的语义,但是该模式的大多数用法将涉及许多对象,因此最好使用a ThreadLocal来保存对将对象映射到每个线程实例的哈希集的引用。
2014年

@optional这只是意味着非静态的每个实例都ThreadLocal将保存其自己的线程本地数据,即使这些ThreadLocal实例存在于同一线程中也是如此。这样做不一定是错误的-我想这可能是两者中最不受欢迎的模式
geg

17

将其设置为静态,或者尝试避免类中的任何静态字段-将类本身设置为单例,然后只要在全球范围内都可以使用实例级ThreadLocal,就可以安全地使用该实例级的ThreadLocal。



3

原因是通过与线程关联的指针访问变量。它们就像具有线程作用域的全局变量一样起作用,因此,static最适合。这是在诸如pthread之类的线程中获取线程局部状态的方式,因此这可能只是历史和实现的偶然。


1

在每个线程的每个实例的每个线程上使用threadlocal的情况是,如果您希望某个对象在对象的所有方法中均可见,并且使其具有线程安全性,而又不像对普通字段那样对它进行同步访问。


1

参照这个,可以更好地理解。

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

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


-1

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”。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. 以上所有这些线程将休眠3秒
  3. main执行结束并显示当前用户为“ 62”,并行ForkJoinPool.commonPool-worker-1执行结束并显示当前用户为“ 87”,并行ForkJoinPool.commonPool-worker-2执行结束并显示当前用户为“ 31”,并行ForkJoinPool.commonPool-worker-3执行结束并显示当前用户为“ 81”

推理

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.