37

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

4

6 回答 6

83

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

最好通过一个例子来解释。假设您有一个 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;
  }
}
于 2009-09-29T06:54:24.350 回答
15

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

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

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

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

于 2009-09-29T11:42:23.073 回答
8

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

现在还有更多内容,但正如您所见,返回的值是由当前线程决定的。这就是为什么它对每个线程都是本地的。

于 2009-09-29T06:39:15.930 回答
6

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

于 2009-09-29T06:55:31.797 回答
2

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

不要忘记,有一些 JAVA API 结构,它们不是线程安全的,例如DateFormat。DateFormat 的静态实例在服务器端不起作用。

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

于 2009-09-29T07:01:38.767 回答
1

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

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

于 2009-09-29T06:49:47.150 回答