关于 Java 中的 Threadlocal 的安全性



目前,我有一个网络项目。我将一个变量(如sessionid)保存在SpringMVC拦截器的threadlocal中,并在postHandle方法中删除它。但我想知道这是否安全。 例如,如果一个线程保存了一个会话ID,那么CPU上下文切换发生了,在这种情况下,其他东西会占用这个线程并设置另一个会话ID或在postHandle中删除它。当我们切换回来时,会话 ID 发生了变化。如果这是可能的,我们还有其他解决方案吗?

@Interceptor
public class BusinessInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
ThreadLocalUtil.contextThreadLocal.set(createSessionId());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex)
throws Exception {
ThreadLocalUtil.contextThreadLocal.remove();
}
}

您对上下文切换的含义感到有些困惑。仅从维基百科来看,它被定义为:

在多任务上下文中,它指的是存储 一个任务的系统状态,以便该任务可以暂停,另一个任务 恢复。

存储一个任务的系统状态意味着存储线程有权访问的所有值,这当然包括线程局部值。因此,上下文切换是关于共享 CPU 时间,而不是关于跨线程共享数据。(嗯,是的,当操作系统从用户模式移动到内核模式时,还有上下文切换,但我想这不是 OP 在他脑海中的那种)。

此外,线程局部变量将保存不同的值,具体取决于有权访问它的线程。如果我们看一下Java中ThreadLocal的定义:

这些变量与它们的正常对应变量不同,因为每个变量 访问一个线程(通过其getset方法)有自己的,变量的独立初始化副本。

所以,答案是:不,没有其他线程能够读取该线程局部变量的值,而与正在发生的上下文切换的数量无关。


在另一个说明中,我会担心您在代码中所做的假设。我的意思是,在每个请求的线程本地存储一个值让我认为您假设每个请求执行模型都有一个线程。此外,依靠使用线程局部变量来传递值等同于使用全局变量,但奇怪的是,它们更不像部分全局变量,并且在运行时通常会带来有趣的惊喜。

您必须意识到,在这个时代,我们拥有异步/反应式执行模型,HTTP/2等,您的假设将落空,因为您将有多个线程执行给定的请求...正因为如此,当任何其他线程尝试访问它时,您的线程局部变量将未初始化或保留完全意外的值。这将带来很多头痛。

我建议您找到一种传递会话 ID的不同方式,并且建议您将其作为参数显式地传递给可能需要它的后续函数。

也许你可以尝试这种方式将参数从preHandle传递到postHandle:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("xxx", xxx);
return true;
}

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Object obj = request.getAttribute("xxx");
request.removeAttribute("xxx");
}

最新更新