一个使用线程局部存储(ThreadLocal)技术导致用户会话信息泄露案例的剖析
我们的系统是一个B/S架构的WEB系统,采用的是类似struts的基于action的WEB框架,近期系统上线后碰到了一个用户会话信息泄露的问题,虽然问题最终于半天后得到了解决,但对此问题的剖析有利于我们更深地理解与多线程并发相关的线程局部存储(ThreadLocal)技术,故特撰此文与大家共飨。
线程局部存储(ThreadLocal)技术是多线程技术中用于解决并发问题的一个最轻量级且使用起来最简单的技术。其原理是将一块内存与线程关联,每个线程访问的的变量都存在于本线程的局部存储区中,因此多个线程间访问相同的变量名时不会产生并发问题。对应就到java中,类ThreadLocal就是JVM用来实现线程局部存储的。
我们磁到的问题是这样的,正常情况下用户登录后系统首页会显示用户的账户等信息,但某用户登录后发现其首页显示的却是其他人的信息。当其再次刷新首页后,页面信息显示却又恢复了正常。发生这样的问题后,我们在测试环境下进行了测试,分别使用两个用户在不同的浏览器中登录系统,并同时刷新首页,此时问题被复现了,而且发生此问题的机率还比较高,粗略估计每10笔就有一两笔发生。另外测试中还发现一个现象比较值得注意,就是此问题并非是并发情况下才会发生,当一个用户未发送任何交易时,另一用户多次刷新页面后还有可能会显示前一用户的信息。
经验告诉我们,如果一个问题有时发生有时不发生,而且发生的机率不是很高,那么该问题很有可能与多线程并发有关。问题发生后我们首先想到是否是程序中的交易处理类未考虑多线程并发呢?最后的结果表明这个问题确实与多线程并发有关,但却并非是交易类未考虑并发而导致的,事实上系统中的所有交易类都是线程安全的(类似Webwork中的Action类),根本不需考虑多线程并发的问题。为了更好地让大家思考这个问题,下面先描述一下系统中交易的基本处理流程,如下图:
----> EncodingFilter, UserSessionFilter ---> MainServlet ---> Transaction ---> JSP
用户发起的交易首先经过一组过滤器进行交易的通用处理,其中包括字符集转换过滤器、用户会话处理过滤器等,其中用户会话过滤器实现了基于线程局部存储技术的用户会话访问(后面会详细描述)。过滤器处理后所有交易全部交由一个主控的Sevlet根据交易名进行交易的转发。具体交易处理类调用相关领域对象实现交易处理,并为JSP页面准备展示所需数据。
在上述过程中,因为交易类需要频繁访问用户会话信息,比如获取当前用户的权限信息、获取当前用户的帐户信息等,为了减少参数传递,系统中的RequestContext类实现了获取上述对象的便捷方法。以下是该类的部分方法:
public class RequestContext {
private static ThreadLocal context = new ThreadLocal() {
protected synchronized Object initialValue() {
return new RequestContext();
}
};
public static RequestContext get() {
return (RequestContext) context.get();
}
public User getUser();
public UserSession getSession();
public String getIP();
...
}
各交易类使用如下方式获取所需信息:
User user = RequestContext.get().getUser();
UserSession session = RequestContext.get().getIP();
String ip = RequestContext.get().getIP();
...
系统通过用户会话过滤器拦截所有经由主控Servlet处理的交易,在交易处理前将用户信息注入到一个RequestContext的实例中,然后将该实例与当前线程绑定,这样随后的交易类就可以便利地访问用户会话信息了,相关代码如下:
public class UserSessionFilter implements Filter {
...
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
...
UserSession session = getSession();
User user = getUserFromSession(session);
RequestContext.get().clear();
RequestContext.get().setSession(session);
RequestContext.get().setUser(user);
...
chain.doFilter(request, response);
...
}
...
}
以上对系统的交易处理流程作了一个大致的介绍,那么回到文章开头的问题中,是什么导致了用户会话信息的泄露呢?也就是说是什么导致了另一个用户可以访问其它用户的RequestContext中的数据呢?
答案相当简单,就是因为这个首页交易只是一个纯粹的JSP页面,该交易并未经过用户会话过滤器的处理。有人可能会问,既然该页面并未经过滤器处理,那么该JSP页面对应的处理线程的RequestContext中就不应该有任何用户信息,这样JSP页面上就应该不显示任何内容才对,为什么页面上反而会显示出其它人的用户信息呢?要解答这个问题就要先了解应用服务器是如何使用线程技术处理用户请求的。应用服务器收到一个用户请求后,总是分配一个独立的线程对该请求进行处理,考虑到频繁创建和销毁线程的开销太大,一般应用服务器都会有一个高效的线程池系统来回收已完成处理的请求线程,也就是说当某个请求被处理完后,相应线程并不会被销毁,而是被返回到线程池中以再次响应其它请求。这样一说,大家是不是就明白问题原因所在了呢?
是的,当某个用户提交访问页面的请求时,应用服务器会从线程池中取得一个空闲线程以处理该请求,如果此时分配的线程是曾经响应过其它用户请求的线程时,该线程的局部存储中就还保留有其它用户的用户信息,因为系统中所有交易都经由会话过滤器处理过,所以当执行流程转到交易类时,线程的局部存储中已经有了正确的用户信息,此时并不会产生任何问题。而一旦所访问的交易没有经过会话过滤器处理时,页面上就出现仍然存留于线程局部存储中的其它用户的信息了。
一旦问题的原因分析清楚了,要解决就很容易。
通过以上的剖析,你是否对线程局部存储(ThreadLocal)、线程池等技术有了更深的理解呢?欢迎大家多谈谈自己的看法。
分享到:
相关推荐
简单分析Java线程编程中ThreadLocal类的使用共4页.pdf.zip
问题背景在 Tomcat 中,下面的代码都在 webapp 内,会导致 WebappClassLoaderWebappClassLoader 泄漏,无法被回收。
主要介绍了详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、...
java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多...
导致JVM内存泄露的ThreadLocal详解 为什么要有ThreadLocal ThreadLocal的使用 实现解析 引发的内存泄漏分析 错误使用ThreadLocal导致 线程不安全分析
本例以序列号生成的程序为例,展示ThreadLocal的使用
ThreadLocal入门教程。 讲解了线程安全和ThreadLocal的使用的基本知识。
其实,它就是一个容器,用于存放线程的局部变量,我认为应该叫做 ThreadLocalVariable(线程局部变量)才对,真不理解为什么当初 Sun 公司的工程师这样命名。 早在 JDK 1.2 的时代,java.lang.ThreadLocal 就诞生了...
多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!线程锁主要用来给方法、代码块加锁
ThreadLocal应用示例及理解,这个写了相关的示例,可以参考一下。
我们可以看到,通过这段代码实例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用...
JavaEE DBUtil结合ThreadLocal的一个案例
今天小编就为大家分享一篇关于Hibernate用ThreadLocal模式(线程局部变量模式)管理Session,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. ...
ThreadLocal则为每一个线程提供了一个变量副本,从而隔离了多个线程访问数据的冲突,ThreadLocal提供了线程安全的对象封装,下面我们就来详细了解一下吧
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量
主要介绍了Java中的线程同步与ThreadLocal无锁化线程封闭实现,Synchronized关键字与ThreadLocal变量的使用是Java中线程控制的基础,需要的朋友可以参考下
通常情况下,我们创建的成员变量都是线程不安全的。因为他可能被多个线程同时修改,此变量对于多...而使用ThreadLocal创建的变量只能被当前线程访问,其他线程无法访问和修改。也就是说:将线程公有化变成线程私有化。