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