ThreadLocal 的目的? [复制]

Posted

技术标签:

【中文标题】ThreadLocal 的目的? [复制]【英文标题】:Purpose of ThreadLocal? [duplicate] 【发布时间】:2010-12-02 05:34:55 【问题描述】:

给定here 的 ThreadLocal 的目的表明该变量对于任何访问包含 ThreadLocal 变量的对象的线程都是本地的。将 ThreadLocal 变量作为类的成员,然后使其成为 Thread 的本地变量,而不是 Thread 本身的局部变量,这有什么区别?

【问题讨论】:

【参考方案1】:

线程是一个执行单元,因此多个线程可以同时执行相同的代码。如果多个线程同时在一个对象/实例上执行,它们将共享实例变量。每个线程都有自己的局部变量,但如果不传递参数,很难跨对象共享这些变量。

最好通过一个例子来解释。假设您有一个 Servlet,它获取登录用户,然后执行一些代码。

doGet(HttpServletRequest req, HttpServletResponse resp) 
  User user = getLoggedInUser(req);
  doSomething()
  doSomethingElse()
  renderResponse(resp)

现在如果 doSomething() 方法需要访问用户对象会发生什么?您不能将用户对象设为实例或静态变量,因为每个线程将使用相同的用户对象。您可以将用户对象作为参数传递,但这很快就会变得混乱,并将用户对象泄漏到每个方法调用中:

doGet(HttpServletRequest req, HttpServletResponse resp) 
  User user = getLoggedInUser(req);
  doSomething(user)
  doSomethingElse(user)
  renderResponse(resp,user)

更优雅的解决方案是将用户对象放入 ThreadLocal

doGet(HttpServletRequest req, HttpServletResponse resp) 
  User user = getLoggedInUser(req);
  StaticClass.getThreadLocal().set(user)
  try 
    doSomething()
    doSomethingElse()
    renderResponse(resp)
  
  finally 
    StaticClass.getThreadLocal().remove()
  

现在任何需要用户对象的代码都可以通过从线程本地提取它来获取它,而无需求助于那些讨厌的额外参数:

User user = StaticClass.getThreadLocal().get()

如果您使用这种方法,请注意在 finally 块中再次删除对象。否则,用户对象可能会在使用线程池的环境中徘徊(例如 Tomcat 应用服务器)。

编辑:静态类的代码

class StaticClass 
  static private ThreadLocal<User> threadLocal = new ThreadLocal<>();

  static ThreadLocal<User> getThreadLocal() 
    return threadLocal;
  

【讨论】:

这里的StaticClass是什么?。扩展 Thread 的类? StaticClass 只是获取 ThreadLocal 对象的一种便捷方式。我将编辑示例以包含 StaticClass 的代码。 我不会说它总是更优雅,但它更方便 那么这个对象会被多个线程共享吗?【参考方案2】:

您必须意识到扩展 Thread 的类的实例与实际的 Java 线程(可以想象为“执行指针" 贯穿您的代码并执行它)。

这样一个类的实例代表一个Java线程并允许操作它(例如中断它),但除此之外它们只是常规对象,并且它们的成员可以从所有线程访问可以获得对该对象的引用(即not hard)。

当然,您可以尝试保持成员私有,并确保它仅由 run() 或从中调用的方法使用(也可以从其他线程调用公共方法),但这很容易出错并且对于不想将数据全部保存在 Thread 子类中的更复杂的系统而言,这并不可行(实际上,您不应该将 Thread 子类化,而是使用 Runnable)。

ThreadLocal 是一种简单、灵活的方式,可以让其他线程无法同时访问每个线程的数据,而无需付出很大的努力或在设计上妥协。

【讨论】:

【参考方案3】:

Thread 对象可以具有内部数据成员,但任何拥有(或可以获得)对 Thread 对象的引用的人都可以访问这些数据成员。一个 ThreadLocal 故意只与访问它的每个 Thread 相关联。优点是没有并发问题(在 ThreadLocal 的上下文中)。线程的内部数据成员具有与任何共享状态相同的并发问题。

让我解释一下将结果与特定线程相关联的想法。 ThreadLocal 的本质是这样的:

public class MyLocal<T> 
  private final Map<Thread, T> values = new HashMap<Thread, T>();

  public T get() 
    return values.get(Thread.currentThread());
  

  public void set(T t) 
    values.put(Thread.currentThread(), t);
  

现在不仅如此,但您可以看到返回的值是由当前线程决定的。这就是它对每个线程本地的原因。

【讨论】:

故意关联每个线程的含义?线程本身是一个对象,对吗?那么与线程相关联实际上意味着与线程对象相关联吗? @Ajay:是的。 ThreadLocal 实例通过附加到 Thread 对象的半私有数据结构与 Thread 对象相关联;查看“包私有”成员 Thread.threadLocals 和 Thread.inherittableThreadLocals @Stephen:那么与拥有一个线程类的成员相比,拥有一个 threadLocal 有什么不同。 Thread 子类的实例与任何其他对象没有什么不同,它的成员可以被任何和所有线程访问。【参考方案4】:

有一个***entry about这个问题区域。在我们的环境中,它通常用于保持本地请求。在服务器端,请求主要由单个线程处理。为了保持本地化,您可以放置​​数据,例如会话数据,在线程局部变量中。此数据对其他请求(线程)是不可见的,因此您不需要将其与其他请求同步。

别忘了,有 JAVA API 构造,它们不是线程安全的,例如DateFormat。 DateFormat 的静态实例在服务器端不起作用。

事实上,使用自己的私有数据副本处理多线程编程比使用锁和监视器处理更容易。

【讨论】:

【参考方案5】:

ThreadLocal 在 web 应用程序中非常有用。典型的模式是在 Web 请求处理开始的某个地方(通常在 servlet 过滤器中)状态存储在 ThreadLocal 变量中。因为请求的所有处理都在 1 个线程中完成,所以参与请求的所有组件都可以访问此变量。

【讨论】:

【参考方案6】:

ThreadLocals 的优点是它们可以被在普通线程上运行的方法使用......或线程的任何子类。

相比之下,如果您的线程局部变量必须作为 Thread 的自定义子类的成员来实现,那么您无法做很多事情。例如,如果你的应用程序需要在预先存在的 vanilla Thread 实例上运行方法,它就会遇到麻烦;即由应用程序编写者未编写且无法修改的某些库代码创建的实例。

【讨论】:

以上是关于ThreadLocal 的目的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

深入剖析ThreadLocal

多线程之ThreadLocal

ThreadLocal原理

ThreadLocal和内存泄漏

Java并发编程:深入剖析ThreadLocal

转:Java并发编程:深入剖析ThreadLocal