此处给出的 ThreadLocal 的目的表明该变量对于任何访问包含 ThreadLocal 变量的对象的线程都是本地的。它有什么区别,将 ThreadLocal 变量作为类的成员,然后使其成为 Thread 的本地变量,而不是 Thread 本身的局部变量?
6 回答
线程是一个执行单元,因此多个线程可以同时执行相同的代码。如果多个线程同时在一个对象/实例上执行,它们将共享实例变量。每个线程都有自己的局部变量,但如果不传递参数,很难跨对象共享这些变量。
最好通过一个例子来解释。假设您有一个 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;
}
}
您必须意识到扩展 Thread 的类的实例与实际的 Java 线程不同(可以将其想象为贯穿您的代码并执行它的“执行指针”)。
这种类的实例代表一个 Java 线程并允许对其进行操作(例如中断它),但除此之外它们只是常规对象,并且它们的成员可以从可以获得对该对象的引用的所有线程中访问(这并不难)。
当然,您可以尝试保持成员私有,并确保它仅由run()
从它调用的 or 方法使用(公共方法也可以从其他线程调用),但这很容易出错,并且对于更多复杂的系统,您不想将数据全部保存在 Thread 子类中(实际上您不应该将 Thread 子类化,而是使用 Runnable 代替)。
ThreadLocal 是一种简单、灵活的方式,可以让每个线程的数据无法被其他线程同时访问,而不需要付出很大的努力或设计妥协。
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);
}
}
现在还有更多内容,但正如您所见,返回的值是由当前线程决定的。这就是为什么它对每个线程都是本地的。
ThreadLocal 在 web 应用程序中非常有用。典型的模式是在 Web 请求处理开始的某个地方(通常在 servlet 过滤器中)状态存储在 ThreadLocal 变量中。因为请求的所有处理都在 1 个线程中完成,所以参与请求的所有组件都可以访问此变量。
有一个关于这个问题区域的维基百科条目。在我们的环境中,它通常用于保持本地请求。在服务器端,请求主要由单个线程处理。为了保持本地化,您将数据(例如会话数据)放在线程局部变量中。此数据对其他请求(线程)是不可见的,因此您不需要将其与其他请求同步。
不要忘记,有一些 JAVA API 结构,它们不是线程安全的,例如DateFormat。DateFormat 的静态实例在服务器端不起作用。
事实上,当您使用自己的私有数据副本时,处理多线程编程比使用锁和监视器处理更容易。
ThreadLocals 的优点是它们可以被在普通线程上运行的方法使用......或线程的任何子类。
相比之下,如果您的线程局部变量必须作为 Thread 的自定义子类的成员来实现,那么您无法做很多事情。例如,如果你的应用程序需要在预先存在的 vanilla Thread 实例上运行方法,它就会遇到麻烦;即由应用程序编写者未编写且无法修改的某些库代码创建的实例。