ThreadLocal 的目的?

2022-09-01 04:47:05

此处给出的 ThreadLocal 的用途指出,该变量对于访问包含 ThreadLocal 变量的对象的任何 Thread 都是本地的。将 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()

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

编辑:静态类的代码

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

  static ThreadLocal<User> getThreadLocal() {
    return threadLocal;
  }
}

答案 2

你必须意识到,扩展 Thread 的类的实例与实际的 Java 线程不是一回事(Java 线程可以想象为一个“执行指针”,它贯穿你的代码并执行它)。

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

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

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