被我们忽略的HttpSession线程安全问题

1. 背景

最近在读《Java concurrency in practice》(Java并发实战),其中1.4节提到了Java web的线程安全问题时有如下一段话:

Servlets and JPSs, as well as servlet filters and objects stored in scoped containers like ServletContext and HttpSession, 
simply have to be thread-safe.

Servlet, JSP, Servlet filter 以及保存在 ServletContext、HttpSession 中的对象必须是线程安全的。含义有两点:

1)Servlet, JSP, Servlet filter 必须是线程安全的(JSP的本质其实就是servlet);

2)保存在ServletContext、HttpSession中的对象必须是线程安全的;

servlet和servelt filter必须是线程安全的,这个一般是不存在什么问题的,只要我们的servlet和servlet filter中没有实例属性或者实例属性是”不可变对象“就基本没有问题。但是保存在ServletContext和HttpSession中的对象必须是线程安全的,这一点似乎一直被我们忽略掉了。在Java web项目中,我们经常要将一个登录的用户保存在HttpSession中,而这个User对象就是像下面定义的一样的一个Java bean:

复制代码

public class User {private int id;private String userName;private String password;// ... ...public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

复制代码

2. 源码分析

下面分析一下为什么将一个这样的Java对象保存在HttpSession中是有问题的,至少在线程安全方面不严谨的,可能会出现并发问题。

Tomcat8.0中HttpSession的源码在org.apache.catalina.session.StandardSession.java文件中,源码如下(截取我们需要的部分):

复制代码

public class StandardSession implements HttpSession, Session, Serializable {// ----------------------------------------------------- Instance Variables/*** The collection of user data attributes associated with this Session.*/protected Map<String, Object> attributes = new ConcurrentHashMap<>();/*** Return the object bound with the specified name in this session, or* <code>null</code> if no object is bound with that name.** @param name Name of the attribute to be returned** @exception IllegalStateException if this method is called on an*  invalidated session*/@Overridepublic Object  getAttribute(String name) {if (!isValidInternal())throw new IllegalStateException(sm.getString("standardSession.getAttribute.ise"));if (name == null) return null;return (attributes.get(name));}/*** Bind an object to this session, using the specified name.  If an object* of the same name is already bound to this session, the object is* replaced.* <p>* After this method executes, and if the object implements* <code>HttpSessionBindingListener</code>, the container calls* <code>valueBound()</code> on the object.** @param name Name to which the object is bound, cannot be null* @param value Object to be bound, cannot be null* @param notify whether to notify session listeners* @exception IllegalArgumentException if an attempt is made to add a*  non-serializable object in an environment marked distributable.* @exception IllegalStateException if this method is called on an*  invalidated session*/public void setAttribute(String name, Object value, boolean notify) {// Name cannot be nullif (name == null)throw new IllegalArgumentException(sm.getString("standardSession.setAttribute.namenull"));// Null value is the same as removeAttribute()if (value == null) {removeAttribute(name);return;}// ... ...// Replace or add this attributeObject unbound = attributes.put(name, value);// ... ...}/*** Release all object references, and initialize instance variables, in* preparation for reuse of this object.*/@Overridepublic void recycle() {// Reset the instance variables associated with this Sessionattributes.clear();// ... ...}/*** Write a serialized version of this session object to the specified* object output stream.* <p>* <b>IMPLEMENTATION NOTE</b>:  The owning Manager will not be stored* in the serialized representation of this Session.  After calling* <code>readObject()</code>, you must set the associated Manager* explicitly.* <p>* <b>IMPLEMENTATION NOTE</b>:  Any attribute that is not Serializable* will be unbound from the session, with appropriate actions if it* implements HttpSessionBindingListener.  If you do not want any such* attributes, be sure the <code>distributable</code> property of the* associated Manager is set to <code>true</code>.** @param stream The output stream to write to** @exception IOException if an input/output error occurs*/protected void doWriteObject(ObjectOutputStream stream) throws IOException {// ... ...// Accumulate the names of serializable and non-serializable attributesString keys[] = keys();ArrayList<String> saveNames = new ArrayList<>();ArrayList<Object> saveValues = new ArrayList<>();for (int i = 0; i < keys.length; i++) {Object value = attributes.get(keys[i]);if (value == null)continue;else if ( (value instanceof Serializable)&& (!exclude(keys[i]) )) {saveNames.add(keys[i]);saveValues.add(value);} else {removeAttributeInternal(keys[i], true);}}// Serialize the attribute count and the Serializable attributesint n = saveNames.size();stream.writeObject(Integer.valueOf(n));for (int i = 0; i < n; i++) {stream.writeObject(saveNames.get(i));try {stream.writeObject(saveValues.get(i));  // ... ...            } catch (NotSerializableException e) {// ... ...               }}}
}

复制代码

我们看到每一个独立的HttpSession中保存的所有属性,是存储在一个独立的ConcurrentHashMap中的:

protected Map<String, Object> attributes = new ConcurrentHashMap<>();

所以我可以看到 HttpSession.getAttribute(), HttpSession.setAttribute() 等等方法就都是线程安全的。

另外如果我们要将一个对象保存在HttpSession中时,那么该对象应该是可序列化的。不然在进行HttpSession的持久化时,就会被抛弃了,无法恢复了:

            else if ( (value instanceof Serializable)
                    && (!exclude(keys[i]) )) {
                saveNames.add(keys[i]);
                saveValues.add(value);
            } else {
                removeAttributeInternal(keys[i], true);
            }

所以从源码的分析,我们得出了下面的结论:

1)HttpSession.getAttribute(), HttpSession.setAttribute() 等等方法都是线程安全的;

2)要保存在HttpSession中对象应该是序列化的;

虽然getAttribute,setAttribute是线程安全的了,那么下面的代码就是线程安全的吗?

session.setAttribute("user", user);

User user = (User)session.getAttribute("user", user);

不是线程安全的!因为User对象不是线程安全的,假如有一个线程执行下面的操作:

User user = (User)session.getAttribute("user", user);

user.setName("xxx");

那么显然就会存在并发问题。因为会出现:有多个线程访问同一个对象 user, 并且至少有一个线程在修改该对象。但是在通常情况下,我们的Java web程序都是这么写的,为什么又没有出现问题呢?原因是:在web中 ”多个线程访问同一个对象 user, 并且至少有一个线程在修改该对象“ 这样的情况极少出现;因为我们使用HttpSession的目的是在内存中暂时保存信息,便于快速访问,所以我们一般不会进行下面的操作:

User user = (User)session.getAttribute("user", user);

user.setName("xxx");

我们一般是只使用对从HttpSession中的对象使用get方法来获得信息,一般不会对”从HttpSession中获得的对象“调用set方法来修改它;而是直接调用 setAttribute来进行设置或者替换成一个新的。

3. 结论

所以结论是:如果你能保证不会对”从HttpSession中获得的对象“调用set方法来修改它,那么保存在HttpSession中的对象可以不是线程安全的(因为他是”事实不可变对象“,并且ConcurrentHashMap保证了它是被”安全发布的“);但是如果你不能保证这一点,那么你必须要实现”保存在HttpSession中的对象必须是线程安全“。不然的话,就存在并发问题。

使Java bean线程安全的最简单方法,就是在所有的get/set方法都加上synchronized。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/231089.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用“价值”的视角来看安全:《构建新型网络形态下的网络空间安全体系》

作者简介&#xff1a; 懒大王敲代码&#xff0c;正在学习嵌入式方向有关课程stm32&#xff0c;网络编程&#xff0c;数据结构C/C等 今天给大家介绍《构建新型网络形态下的网络空间安全体系》这本书&#xff0c;希望大家能觉得实用&#xff01; 欢迎大家点赞 &#x1f44d; 收藏…

【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)

系列文章目录 身躯已然冰封&#xff0c;灵魂仍旧火热 本文由睡觉待开机原创&#xff0c;未经允许不得转载。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言&#xff0c;共同进步&#xff01; 文章目录 系列文章目录前言题目链接&#xff08;有需要…

阿里云部署k8s with kubesphere

阿里云ESC 创建实例 填入密码即可 云上的防火墙相关设置就是安全组 vpc 专有网络 划分私有ip 子网 vpc 隔离环境域 不同的vpc下 即使相同的子网也不互通 使用交换机继续划分子网 停止 释放 不收钱 k8s 服务器 4核8G*1 8核16G *2 git 创建凭证 pipeline 发邮箱 (p124)…

双指针算法(二)

三数之和 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重…

Linux---Ubuntu软件安装

1. 软件安装的介绍 Ubuntu软件安装有两种方式: 离线安装(deb文件格式安装&#xff09;在线安装(apt-get方式安装) 2. deb文件格式安装 是 Ubuntu 的安装包格式&#xff0c;可以使用 dpkg 命令进行软件的安装和卸载。 命令说明dpkg安装和卸载deb安装包 dpkg命令选项: 选项…

数字滤波器设计——Matlab实现数字信号处理<1>

目录 一.实验内容 二.代码分析 1.信号产生部分 2.利用傅立叶级数展开的方法&#xff0c;自由生成所需的x(t) 3.通过选择不同的采样间隔T&#xff08;分别选T>或<1/2fc&#xff09;&#xff0c;从x(t)获得相应的x(n) 3.对获得的不同x(n)分别作傅立叶变换&#xff0c…

2024年高效远程协同运维工具推荐

随着企业的不断发展以及变化&#xff0c;企业的内部IT环境也是日益复杂&#xff0c;一跨高效远程协同运维工具必不可少&#xff0c;不仅可以提高生产力&#xff0c;还能降低运营成本。这里就给大家推荐2024年高效远程协同运维工具。 高效远程协同运维工具应用场景 1、IT运维管…

安卓端出现https请求失败(转)

背景# 某天早上&#xff0c;正在一个会议时&#xff0c;突然好几个同事被叫出去了&#xff1b;后面才知道&#xff0c;是有业务同事反馈到领导那里&#xff0c;我们app里面某个功能异常。 具体是这样&#xff0c;我们安卓版本的app是禁止截屏的&#xff08;应该是app里做了拦…

设计模式(三)-结构型模式(2)-桥接模式

一、为何需要桥接模式&#xff08;Bridge&#xff09;? 在软件设计中&#xff0c;存在有两个或多个不同维度的模块时&#xff0c;我们需要将这些模块使用到在一起&#xff0c;来实现一个完整的功能。所谓不同维度的意思就是这些模块所负责的职责是不同的&#xff0c;并且它们…

lua安装

lua安装 1.Lua介绍 特点&#xff1a;轻量、小巧。C语言开发。开源。 设计的目的&#xff1a;嵌入到应用程序当中&#xff0c;提供灵活的扩展和定制化的功能。 luanginx&#xff0c;luaredis。 2.windows安装lua windows上安装lua&#xff1a; 检查机器上是否有lua C:\U…

Kubernetes 的用法和解析 -- 4

一.Deployment 资源详解 如果Pod出现故障&#xff0c;对应的服务也会挂掉&#xff0c;所以Kubernetes提供了一个Deployment的概念 &#xff0c;目的是让Kubernetes去管理一组Pod的副本&#xff0c;也就是副本集 &#xff0c;这样就能够保证一定数量的副本一直可用&#xff0c;…

概率论复习

第一章&#xff1a;随机概率及其概率 A和B相容就是 AB 空集 全概率公式与贝叶斯公式&#xff1a; 伯努利求概率&#xff1a; 第二章&#xff1a;一维随机变量及其分布&#xff1a; 离散型随机变量求分布律&#xff1a; 利用常规离散性分布求概率&#xff1a; 连续性随机变量…

Windows使用VNC Viewer远程桌面Ubuntu【内网穿透】

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

hypervisor display显卡节点card0生成过程

ditsi 配置 lagvm/LINUX/android/vendor/qcom/proprietary/devicetree/qcom direwolf-g9ph.dts #include "direwolf-vm-la.dtsi" direwolf-vm-la.dtsi #include "display/quin-vm-display-la.dtsi" quin-vm-display-la.dtsi //对应/sys/class/drm/card…

推荐几款值得收藏的3DMAX插件

推荐几款值得收藏的3DMAX插件 StairGenerator StairGenerator一键楼梯插件&#xff0c;不需要花费太多的时间&#xff0c;轻松从2D平面图生成3D楼梯模型&#xff0c;生成的楼梯模型细节丰富真实。 【主要功能】 1.简单&#xff1a;轻松实现2D到3D建模。 2.具有最详细三维结…

高级桌面编程(一)

前言 学习心得&#xff1a;C# 入门经典第8版书中的第15章《高级桌面编程》 创建控件并设置样式 1 样式 Style WPF 当中我们可以对每一个控件进行完全的自定义。我们可以随意更改控件外观和功能。提供我们能完成这样的效果与控件的样式&#xff08;Style&#xff09;有着不可分…

人工智能革命:共同探索AIGC时代的未来

一、引言 随着大数据和强大的计算能力的兴起&#xff0c;人工智能技术&#xff08;AI&#xff09;正在快速发展&#xff0c;并为各个领域带来革命性的变化。人工智能与智能计算技术&#xff08;AIGC&#xff09;的融合不仅为企业、科研机构和普通用户提供了巨大的机遇&#xff…

60.Sentinel源码分析

Sentinel源码分析 1.Sentinel的基本概念 Sentinel实现限流、隔离、降级、熔断等功能&#xff0c;本质要做的就是两件事情&#xff1a; 统计数据&#xff1a;统计某个资源的访问数据&#xff08;QPS、RT等信息&#xff09; 规则判断&#xff1a;判断限流规则、隔离规则、降级规…

数字孪生Web3D智慧机房可视化运维云平台建设方案

前言 进入信息化时代&#xff0c;数字经济发展如火如荼&#xff0c;数据中心作为全行业数智化转型的智慧基座&#xff0c;重要性日益凸显。与此同时&#xff0c;随着东数西算工程落地和新型算力网络体系构建&#xff0c;数据中心建设规模和业务总量不断增长&#xff0c;机房管理…

Xcode 15 Assertion failed: (false “compact unwind compressed function offset doesn‘t fit in 24

xcode 真机运行报错&#xff1a;Assertion failed: (false && "compact unwind compressed function offset doesnt fit in 24 bits"), function operator(), file Layout.cpp, line 5758 如下图&#xff1a; 解决办法&#xff1a; 在 targets-->Build …