Session的线程安全问题

导读

服务器虽然能够为每一个会话维护一个Session,在一定程度上确保了线程安全,但是往往在一些细节上还是不能如愿地保证线程安全。这里将详细介绍线程不安全的情况。

HttpServletRequest源码

通过CTRL+鼠标左键,你找到了源码。(Tomcat相关的jar包有三个,直接找需要很久

先来看注释:

Extends the javax.servlet.ServletRequest interface to provide request information for HTTP servlets. The servlet container creates an HttpServletRequest object and passes it as an argument to the servlet's service methods(doGet, doPost, etc).

继承了ServletRequest类,为HTTP Servlet一共请求头信息。这个Servlet容器创建了一个HttpServletRequest对象并作为参数传递给Servlet的服务方法,比如doGet、doPost等。

非常清晰了吧?就算HttpServletRequest是一个接口,他在运行的时候还是创建了一个提供请求头信息的容器。而其中的getSession方法一共有两个,一个是带有create参数的方法,一个是无参方法。

首先是有参方法

public HttpSession getSession(boolean create)

他的注释是:传入true,就会在必要的时候为请求新建一个Session,返回值将会是与请求头相关联的HttpSession对象;而如果是false则会在当前没有Session的时候直接返回null,最后也会因为没有对应的有效对象直接返回null

而无参方法则是getSession(true)的调用。

HttpSession的源码

你似乎还是不明白这到底是怎么回事。于是,为了弄清楚getSession到底返回了个什么,你又找到了HttpSession的源码,然后发现他依然是一个接口。

老规矩,先来看看注释:

Provides a way to identify a user across more than one page request or visit to a Web site and to store information about that user.

提供了在多个页面请求中确认其中一个用户身份的方法,或者说是访问Web服务器并存储用户信息的方法。

说白了这就是实现了Session的功能。下面更是有一句你非常熟悉的:The session persists for a specified time period, across more than one connection or page request from the user.。相信不翻译大家也能明白HttpSession是个啥玩意了吧。

但是接口怎么实现?继续顺藤摸瓜,你找到了StandardSession,这部分参考了CSDN中digdeep的博客

而在StandardSession中,第一行就使用了ConcurrentHashMap作为容器存储信息。这是个什么?因为这又是一个大坑所以还是另外用一个单独的版块来进行阐述,点击这里查看ConcurrentHashMap详解。这里就直接说结论了:ConcurrentHashMapHttpSession保证了线程安全,这个是不容置疑的。

Session线程不安全的真正原因

事实上,Servlet本身不是线程安全的,就算你的Session再怎么线程安全,最后Servlet中修改变量值的时候永远都会出现问题。举个例子:

// 在Servlet中定义属性
private int count;
// 在Servlet中编写方法
public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, InterruptedException {
    // 属性修改
    count++;
    // 线程暂停,放大效果
    Thread.sleep(1000);
    // 输出结果
    System.out.println(Thread.currentThread().getName() + "count: " + count);
}
/**
 * 使用tomcat自带的JMETER压力测试工具模拟五次访问,输出结果就是这样:
 * http-bio-8080-exec-6 count: 5
 * http-bio-8080-exec-9 count: 5
 * http-bio-8080-exec-8 count: 5
 * http-bio-8080-exec-5 count: 5
 * http-bio-8080-exec-7 count: 5
 */

很明显,五次输出中四次都拿到了脏数据,这不是能够让系统中断的错误,这是逻辑错误,往往比编译器报错更让人头疼。会出现这种情况的原因有两点:

  • 容器只会创建一个Servlet实例,也就是说,这个Servlet中定义的所有变量,只会出现一次。
  • 容器收到请求之后,会启动一个线程来处理该请求。当有多个请求同时访问同一个Servlet值后,这只会出现一次的变量在被多个线程修改的时候就会出现线程安全问题。

所以这个问题实际上有点迷惑,真正线程不安全的并不是Session,而是Servlet

那么怎么解决呢?加上关键字synchronized

// 在Servlet中定义属性
private int count;
// 在Servlet中编写方法
public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 加锁,this是指当前类
    synchronized (this) {
        count++;
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + "count: " + count);
    }
}
/**
 * 使用tomcat自带的JMETER压力测试工具模拟五次访问,输出结果就是这样:
 * http-bio-8080-exec-5 count: 1
 * http-bio-8080-exec-9 count: 2
 * http-bio-8080-exec-7 count: 3
 * http-bio-8080-exec-8 count: 4
 * http-bio-8080-exec-6 count: 5
 */

现在就正常了。

虽然加锁能够避免线程安全问题,但是加锁会牺牲性能

所以尽量我们不要修改Servlet的属性。如果你使用了框架,相对应的就是Controller部分,尽量不要修改这些部分。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ordinary_brony

代码滞销,救救码农

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值