Servlet 线程安全与并发编程深度解析
Servlet 线程安全与并发编程深度解析
一、Servlet 线程安全机制与风险场景
1.1 Servlet 容器工作原理
- 单实例多线程模型:每个Servlet在容器中只有一个实例,通过线程池处理并发请求
- 请求处理流程:
- 接收HTTP请求创建
HttpServletRequest
和HttpServletResponse
- 从线程池获取工作线程
- 调用
service()
方法处理请求 - 返回响应后线程归还线程池
- 接收HTTP请求创建
1.2 线程安全风险根源
风险类型 | 典型场景 | 后果示例 |
---|---|---|
实例变量共享 | Servlet中定义成员变量 | 数据竞争导致脏读/丢失更新 |
静态变量共享 | 全局计数器、缓存容器 | 统计结果不准确 |
共享对象传递 | 将非线程安全对象传递给其他组件 | 并发修改异常 |
资源未正确关闭 | 数据库连接未及时释放 | 连接池耗尽导致系统瘫痪 |
1.3 典型风险场景分析
场景1:成员变量共享
public class UnsafeServlet extends HttpServlet {private int counter = 0; // 危险!所有线程共享protected void doGet(HttpServletRequest req, HttpServletResponse resp) {counter++;// 多线程下counter值不可预测}
}
场景2:共享非线程安全工具类
public class DateServlet extends HttpServlet {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 非线程安全protected void doGet(...) {Date date = sdf.parse(request.getParameter("date")); // 可能抛出异常}
}
场景3:缓存管理不当
public class CacheServlet extends HttpServlet {private static Map<String, Object> cache = new HashMap<>(); // HashMap非线程安全protected void doPost(...) {cache.put(key, value); // 并发put可能丢失数据}
}
二、线程安全解决方案体系
2.1 防御性编程原则
- 无状态化设计:业务处理不依赖实例变量
- 局部变量优先:方法内创建的变量线程安全
- 不可变对象:使用String、BigInteger等
- 线程封闭:通过ThreadLocal隔离状态
2.2 同步控制方案对比
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
synchronized | 临界区代码简单 | 实现简单 | 性能差,可能死锁 |
ReentrantLock | 需要高级功能(如超时) | 功能丰富 | 需手动释放锁 |
ReadWriteLock | 读多写少场景 | 提升读性能 | 实现复杂 |
ThreadLocal | 线程级状态隔离 | 无锁高性能 | 内存泄漏风险 |
2.3 实用解决方案示例
方案1:方法同步
public class SafeServlet extends HttpServlet {private int counter = 0;public synchronized void doGet(...) { // 方法级同步counter++;// 业务处理}
}
方案2:使用线程安全容器
public class CacheServlet extends HttpServlet {private static Map<String, Object> cache = new ConcurrentHashMap<>();protected void doPost(...) {cache.put(key, value); // 安全操作}
}
方案3:ThreadLocal隔离
public class DateFormatServlet extends HttpServlet {private static final ThreadLocal<SimpleDateFormat> formatters = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));protected void doGet(...) {SimpleDateFormat sdf = formatters.get(); // 每个线程独立实例Date date = sdf.parse(...);}
}
三、Spring Boot 中 Servlet 的线程安全体现在哪里?
1. Spring Boot 还是使用 Servlet 的单实例多线程模型
Spring Boot 底层是基于 嵌入式 Servlet 容器(如 Tomcat、Jetty、Undertow),这些容器依旧遵循 Servlet 规范,也就是:
-
一个
@RestController
(或者@Controller
)的 bean 实例是 单例(默认是 Spring 单例) -
每个请求来临时,由容器(如 Tomcat)分配线程处理,这些线程会调用 Controller 方法
2. Spring 会为每个请求注入 独立的 request/response 对象
你在 Controller 方法中这样写:
@GetMapping("/hello")
public String hello(HttpServletRequest request) {// request 是每个请求独立的对象return "Hello";
}
这个 request
实际是由 Spring MVC 使用 参数解析器 从当前线程上下文(ThreadLocal)中注入的,线程之间是互不影响的。
3. Spring Boot Controller/Service 中也存在线程安全问题!
Spring 帮你注入的是线程安全的 request,但你写的代码如果有 共享状态(比如成员变量),那依然会有线程安全问题:
@RestController
public class UnsafeController {private int count = 0;@GetMapping("/count")public String count() {count++; // 非线程安全,多线程下会有竞态条件return "count=" + count;}
}
和 Servlet 示例中本质上一样的危险。
四、Spring Boot 中线程安全的体现/处理方式有哪些?
场景 | Spring Boot 中的处理方式 | 安全性体现 |
---|---|---|
Controller 请求处理 | 每个请求分配独立线程 + 参数注入 | 请求无共享状态 |
多个请求共享字段 | 使用局部变量 / ThreadLocal / 并发容器 | 手动保证安全 |
Service 单例共享状态 | 避免使用成员变量做临时状态 | 防御性编程 |
并发控制需求 | 使用 @Async 、synchronized 、锁对象等 | 显式控制 |
五、实战举例:Spring Boot 安全与不安全对比
❌ 不安全示例(成员变量共享)
@RestController
public class BadController {private List<String> list = new ArrayList<>();@PostMapping("/add")public String add(@RequestParam String value) {list.add(value); // 非线程安全return "OK";}
}
✅ 安全改法(使用线程安全容器)
@RestController
public class GoodController {private List<String> list = Collections.synchronizedList(new ArrayList<>());@PostMapping("/add")public String add(@RequestParam String value) {list.add(value); // 安全return "OK";}
}
或者直接用 CopyOnWriteArrayList
。
六、补充:Spring 线程封闭的典型应用
Spring 中常用的 ThreadLocal
场景是:
-
RequestContextHolder
:将请求上下文与线程绑定 -
TransactionSynchronizationManager
:事务同步绑定 -
SecurityContextHolder
:Spring Security 用户上下文绑定
平时在 Controller 里能随便用这些上下文,其实都是 ThreadLocal
实现的线程封闭。
七、不可变对象与线程安全
不可变对象特征
- 所有字段final修饰
- 类声明为final
- 不暴露可变字段
- 构造后状态不可变
String类实现分析
public final class String {private final char value[];private final int hash;public String substring(int beginIndex) {return new String(value, beginIndex, subLen);}
}
final关键字的正确理解
- 引用不可变 vs 对象不可变
final List<String> list = new ArrayList<>(); // 可以修改list内容 list = new LinkedList<>(); // 编译错误final String s = "hello"; s = "world"; // 编译错误
相关文章:
Servlet 线程安全与并发编程深度解析
Servlet 线程安全与并发编程深度解析 一、Servlet 线程安全机制与风险场景 1.1 Servlet 容器工作原理 单实例多线程模型:每个Servlet在容器中只有一个实例,通过线程池处理并发请求请求处理流程: 接收HTTP请求创建HttpServletRequest和HttpS…...
C++学习:六个月从基础到就业——面向对象编程:封装、继承与多态
C学习:六个月从基础到就业——面向对象编程:封装、继承与多态 本文是我C学习之旅系列的第九篇技术文章,主要讨论C中面向对象编程的三大核心特性:封装、继承与多态。这些概念是理解和应用面向对象设计的关键。查看完整系列目录了解…...
光谱相机的成像方式
光谱相机的成像方式决定了其如何获取物体的空间与光谱信息,核心在于分光技术与扫描模式的结合。以下是主要成像方式的分类解析: 一、滤光片切换型 1. 滤光片轮(Filter Wheel) 原理:通过旋转装有多个窄带…...
Excel 中让表格内容自适应列宽和行高
Excel 中让表格内容自适应列宽和行高 目录 Excel 中让表格内容自适应列宽和行高自适应列宽自适应行高在Excel中让表格内容自适应列宽和行高,可参考以下操作: 自适应列宽 方法一:手动调整 选中需要调整列宽的列(如果是整个表格,可点击表格左上角行号和列号交叉处的三角形全…...
android rtsp 拉流h264 h265,解码nv12转码nv21耗时卡顿问题及ffmpeg优化
一、 背景介绍及问题概述 项目需求需要在rk3568开发板上面,通过rtsp协议拉流的形式获取摄像头预览,然后进行人脸识别 姿态识别等后续其它操作。由于rtsp协议一般使用h.264 h265视频编码格式(也叫 AVC 和 HEVC)是不能直接用于后续处…...
Day(21)--网络编程
网络编程 在网络通信协议下,不同计算机上运行的程序,进行的数据传输 应用场景:即使通信、网友对战、金融证券等等,不管是什么场景,都是计算机和计算机之间通过网络进行的数据传输 java.net 常见的软件架构 C/S&am…...
Android主流播放器功能详解
Android主流播放器功能详解 前言 本文将深入介绍Android三大主流播放框架(ijkplayer、ExoPlayer和MediaPlayer)的功能特性和实战应用,帮助你选择合适的播放框架并掌握其使用方法。 三大播放框架概述 播放框架开发方特点适用场景MediaPlayerAndroid官方简单易用,系统内置…...
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记2
目录 一、ArcGIS Engine概述 1、 定义 2、 核心功能 3、 与ArcObjects(AO)的关系 二、开发环境搭建 1、 开发工具要求 2、 关键步骤 三、 ArcGIS Engine核心组件 1、 对象模型 2、 类库分类 四、 第一个AE应用程序(C#示例…...
语音合成(TTS)从零搭建一个完整的TTS系统-第一节-效果演示
一、概述 语音合成又叫文字转语音(TTS-text to speech ),本专题我们记录从零搭建一个完整的语音合成系统,包括文本前端、声学模型和声码器,从模型训练到系统的工程化实现,模型可以部署在手机等嵌入式设备上…...
文章记单词 | 第35篇(六级)
一,单词释义 across [əˈkrɒs] prep. 从一边到另一边;横过;在… 对面;遍及;在… 上;跨越;adv. 从一边到另一边;横过;宽;从… 的一边到另一边;在…...
MySQL Binlog 数据恢复总结
🌲 总入口:你想恢复什么? 恢复类型 ├── 表结构 表数据(整张表被 DROP) │ ├── Binlog 中包含 CREATE TABLE │ │ └── ✅ 直接用 mysqlbinlog 提取建表 数据语句,回放即可 │ └── B…...
【Linux】进程基础入门指南(下)
> 🍃 本系列为Linux的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:【小编的个人主页】 >小编将在这里分享学习Linux的心路历程✨和知识分享🔍 >如果本篇文章有不足,还请多多包涵&a…...
NoETL×大模型:Aloudata重构数据智能新范式,开启Chat BI新落地之道
在当今数据驱动的时代,企业对于高效、智能的数据处理与分析需求日益增长。随着大模型的兴起,如DeepSeek等,数据智能领域正经历着前所未有的变革。 Aloudata大应科技创始人&CEO周卫林表示,企业的核心竞争力包括人才壁垒、技术…...
算法题(126):前缀和
审题: 本题需要我们将题目给出的数组的数据的[l,r]范围内的数据和打印 思路: 方法一:前缀和 前缀和的思想就是预处理数据,通过空间换时间的方式提高代码效率 第一步:利用数组f将前缀和记录下来,f[i]表示索引…...
选择排序(简单选择排序、堆排序)
简单选择排序(Selection Sort) 1. 算法思想 它通过多次遍历数组,每次从未排序部分中选择最小(或最大)的元素,将其放到已排序部分的末尾(或开头),直到整个数组有序。 2.…...
【JavaEE】Spring AOP的注解实现
目录 一、AOP 与 Spring AOP二、Spring AOP简单实现三、详解Spring AOP3.1 Spring AOP 核心概念3.1.1 切点(Pointcut)3.1.2 连接点(Join Point)3.1.3 通知(Advice)3.1.4 切面(Aspect)…...
【天外之物】加速度与速度的单位向量的内积得到加速度在切向向量上的值
切向加速度的标量值 a T a_T aT 正是加速度矢量 a \mathbf{a} a 与单位切矢量 T ^ \mathbf{\hat{T}} T^ 的内积(点积)。 1. 数学定义 设物体的速度为 v \mathbf{v} v,加速度为 a \mathbf{a} a,单位切矢量为 T ^ \mathbf{…...
eBay 2025春季财报揭示跨境电商新蓝海:五大隐秘品类引爆增长密码
核心数据速览 2024年第一季度,eBay全球商品交易总额(GMV)达255亿美元,同比增长5%。这一增长不仅源于季节性消费回暖,更折射出跨境电商行业在能源转型、供应链重构及消费需求升级中的结构性变革。透过数据ÿ…...
兔子桌面tv版下载-兔子桌面tv版官方app免费下载安装
兔子桌面 TV 版是一款专为智能电视和机顶盒设计的轻量化桌面应用,其界面采用大图标、大字体设计,支持自由调整应用顺序,将常用的影视、游戏 App 置顶,还可通过主题市场下载动态背景,满足用户对电视界面的个性化需求。 …...
绿算轻舟系列FPGA加速卡:驱动数字化转型的核心动力【2】
工业与医疗:精准化的幕后推手 在工业4.0与智慧医疗领域,绿算轻舟FPGA加速卡通过实时信号处理与高精度控制,推动关键场景的技术升级。 工业自动化:在机器视觉质检中,实现亚像素级缺陷检测,产线检测速度大幅…...
ubuntu1804服务器开启ftp,局域网共享特定文件给匿名用户
要在 Ubuntu 18.04 上设置一个 FTP 服务器,满足以下要求: 允许匿名登录(无需账号密码)。指定分享特定目录下的文件。只允许只读下载。 可以使用 vsftpd(Very Secure FTP Daemon)来实现。以下是详细步骤&a…...
k8s中pod报错 FailedCreatePodSandBox
问题现象: 创建容器时出现一下情况 而且删掉控制器的时候pod还会卡住 解决: 将calico的pod重新删掉。其中有1个控制器pod以及3个node pod 删掉后,大概10来秒就重新创建完成了。 然后现在在使用kubectl apply -f 文件.yaml 就可以正常创…...
请详细说明下面训练阶段的差别: Supervised Fine-Tuning、Reward Modeling、PPO、DPO、KTO、Pre-Training
目录 🔧 一、训练阶段总体流程(从底层到上层) 🧠 1. Pre-Training(预训练) 📌 目的: 📚 数据: ⚙️ 方法: 💡 举个例子…...
Go语言入门到入土——一、安装和Hello World
Go语言入门到精通——安装和Hello World 文章目录 Go语言入门到精通——安装和Hello World下载并安装让Go跑起来为你的代码启动依赖跟踪调用外部包总结 下载并安装 下载地址:https://go.dev/dl/ 下载后傻瓜式安装 查看是否安装完成 go version让Go跑起来 创建一个…...
React 入门完全指南:从零开始构建现代 Web 应用
在当今快速发展的前端开发领域,React 凭借其高效的组件化架构和强大的生态系统,已成为最受欢迎的 JavaScript 库之一。根据 2023 年 Stack Overflow 开发者调查,React 连续七年成为最常用的 Web 框架。无论是初创公司还是科技巨头,…...
0701表单组件-react-仿低代码平台项目
文章目录 1 react表单组件1.1 受控组件 (Controlled Components)示例代码: 1.2 非受控组件 (Uncontrolled Components)示例代码: 2 AntD表单组件实战2.1 开发搜索功能2.2 开发注册页2.3 开发登录页2.4 表单组件校验 结语 1 react表单组件 input表单组件…...
Android ViewStub显示VISIBLE与消失GONE,Kotlin(2)
Android ViewStub显示VISIBLE与消失GONE,Kotlin(2) 在 Android ViewStub显示VISIBLE与消失GONE,Kotlin-CSDN博客 基础上完善。 import android.os.Bundle import android.util.Log import android.view.View import android.view…...
跨站脚本(XSS) 的详细分类、对比及解决方案
以下是 跨站脚本(XSS) 的详细分类、对比及解决方案: 一、XSS的分类与详解 1. 反射型XSS(非持久型XSS) 定义:攻击载荷通过URL参数传递,服务器直接返回到页面中,需用户主动触发。 工…...
JVM:程序计数器、虚拟机栈、本地方法栈
一、程序计数器 (1)程序计数器介绍 作用:当线程执行 Java 方法时,程序计数器记录该线程下一条要执行的字节码指令的地址;当线程执行本地方法时,程序计数器的值为未指定(undefined)…...
适配器模式在Java开发中的应用
适配器模式(Adapter Pattern)是设计模式中的一种结构型模式,它允许将一个类的接口转换成客户端所期望的另一个接口。通过这种方式,原本因接口不兼容而无法协同工作的类能够一起工作。适配器模式在Java开发中非常常见,尤…...
(三)谷歌Code as Policies复现(操作记录)
目录 《复现的项目来源》 一、创建虚拟环境 二、下载原项目并修改(非必须) 二、可直接下载修改后的项目 三、配置环境 (1)安装jupyterlab以及内核 (2)安装ffmpeg (3)配置环境…...
驱动学习专栏--字符设备驱动篇--2_字符设备注册与注销
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模 块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示 : static inline int register_chrdev(unsigned int major, const char *name, const…...
奥创中心卸载工具Armoury Crate Uninstall Tool官网下载
为了应对用户对 Armoury Crate 占用资源大、卸载困难等问题的普遍反馈,ASUS 官方提供了一个专门的卸载工具,即 Armoury Crate Uninstall Tool(奥创中心卸载工具)。该工具的主要作用是帮助用户彻底从系统中移除 Armoury Crate 相关的所有组件,…...
【Linux网络】网络基础概念深度解析
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
【NLP 61、大模型应用 —— RAG方法】
生活打不败一个大口吃饭的人! —— 25.4.13 一、模型幻觉问题 模型幻觉(AI Hallucination)是指人工智能模型(尤其是大语言模型)生成看似合理但实际不准确、虚构或与事实不符内容的现象。其本质是模型基于统计概…...
UV工具——小试牛刀
背景 MCP开发使用到 为什么MCP更推荐使用uv进行环境管理? MCP 依赖的 Python 环境可能包含多个模块,uv 通过 pyproject.toml 提供更高效的管理方式,并且可以避免 pip 的一些依赖冲突问题。此外,uv 的包管理速度远超 pip…...
vue3+vite 多个环境配置
同一套代码 再也不用在不同的环境里来回切换请求地址了 然后踩了一个坑 就是env的文件路径是在当前项目下 不是在views内 因为公司项目需求只有dev和pro两个环境 虽然我新增了3个 但是只在这两个里面配置了 .env是可以配置一些公共配置的 目前需求来说不需要 所以我也懒得配了。…...
《分布式软总线架构下,设备虚拟化技术的深度剖析与优化策略》
设备之间的互联互通和协同工作已成为一种趋势。分布式软总线架构作为实现这一目标的关键技术,为不同设备之间的通信和协作提供了基础。而设备虚拟化技术则是在分布式软总线架构下,进一步提升设备资源利用效率的重要手段。本文将深入探讨在分布式软总线架…...
MCP 正当时:FunctionAI MCP 开发平台来了!
作者:封崇 MCP:AI 时代的“操作系统接口” 2024 年 11 月,Anthropic 发布模型上下文协议(MCP),这一开放标准迅速引发开发者社区的"协议觉醒"。其本质是通过标准化接口实现 LLM 与外部世界的双向…...
AI Agents系列之AI代理的类型
在本文中,我们将探讨不同类型的 AI 代理,包括它们的实现、实际应用、优势和局限性。从简单反射代理到多代理系统,我们将了解这些模型如何推动自动化、决策制定和智能问题解决。 文章目录 1. AI代理的类型1.1 简单反射代理1.1.1 实现**1.1.2 优势****1.1.3 局限性**1.2 基于…...
Go RabbitMQ基础教程:入门与实践指南,实战代码讲解
简介: RabbitMQ是一款实现高级消息队列协议(AMQP)的消息代理软件,也称为消息队列或消息中间件。它通过解耦应用程序之间的直接通信,支持异步数据交换,增强了系统的可扩展性和灵活性。RabbitMQ能够跨平台运…...
LeetCode详解之如何一步步优化到最佳解法:27. 移除元素
LeetCode详解系列的总目录(持续更新中): LeetCode详解之如何一步步优化到最佳解法:前100题目录(更新中...)-CSDN博客 LeetCode详解系列的上一题链接: LeetCode详解之如何一步步优化到最佳解法…...
c++原子操作
原子操作,顾名思义,该操作不可分割。多线程环境也能保证读写数据不错乱。百度搜索了下,其核心概念如下: 1、不可分割性。原子操作是指一系列不可被CPU上下文交换的机器指令,操作要么完全执行,要么完全不执…...
在 Redis Lua 脚本中,keyCount 参数的作用是明确区分脚本参数中的 KEYS 和 ARGV,具体关系如下:
在 Redis Lua 脚本中,keyCount 参数的作用是**明确区分脚本参数中的 KEYS 和 ARGV**,具体关系如下: --- ### 核心作用 1. **参数分类标识** - keyCount 表示脚本中使用的 Redis KEY 的数量(即 KEYS 数组的长度)…...
小白如何从0学习CSS
以下是针对小白从零开始系统学习 CSS 的完整路径和实用指南,结合核心概念、实践技巧和项目经验,助你掌握网页样式的精髓: 1. 理解 CSS 是什么? 定义:CSS(层叠样式表)用于控制网页的视觉表现&…...
一文掌握RK3568开发板Android13挂载Windows共享目录
在物联网和边缘计算场景中,开发板与PC端的高效文件交互尤为重要。现以iTOP-RK3568开发板为例,详细演示Android13系统如何通过CIFS协议挂载Windows共享目录,实现开发板与PC的无缝文件共享。 RK3568开发板优势 iTOP-3568开发板采用瑞芯微RK3…...
UE5烘培后->为什么C磁盘满了
烘培会产生ddc 需要把路径切换一下,比如切换到游戏空间下。 如何修改,修改如下: 使用记事本打开BaseEngine.ini文件。 将以下内容: textCopy Code Path\"%ENGINEVERSIONAGNOSTICUSERDIR%DerivedDataCache\" 替换为&…...
本地搭建全网可访问的开源音乐服务器Melody结合内网穿透随时听歌
文章目录 前言1. 添加镜像源2. 本地部署Melody3. 本地访问与使用演示4. 安装内网穿透5. 配置Melody公网地址6. 配置固定公网地址 前言 嗨,各位音乐发烧友们!今天我要带你们解锁一个超酷的新技能——在香橙派Zero3上搭建自己的在线音乐平台,并…...
深度学习Y5周:yolo.py文件解读
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 一、前言 文件位置:./models/yolo.py 此文件是实现YOLOv5网络模型的搭建文件,如果想改进YOLOv5,这个文件是必须进行修改的…...
Qt实现文件传输服务器端(图文详解+代码详细注释)
Qt实现文件传输服务器 1、前言2、服务器2.1 服务器UI界面2.2添加网络模块和头文件2.3、创建服务器对象2.4 连接有新连接的信号与槽2.5实现有新连接处理的槽函数2.6 选择文件按钮实现2.6.1 连接按钮点击的信号与槽2.6.2 添加头文件2.6.3 创建所需对象2.6.3 选择文件按钮实现 2.7…...