Servlet的多线程同步问题
servlet 是单实例多线程的工作模式:服务器只会为每个 Servlet 创建一个实例对象,所有客户端请求都会复用这个实例,由多个线程并发调用service()/doGet()/doPost()方法处理请求。
这就带来了多线程同步问题:如果 Servlet 中定义了实例变量,多个线程同时读写会引发数据错乱、线程安全问题。
一、核心原理:为什么会有线程安全问题?
Servlet 生命周期
- 服务器启动 / 第一次访问时:创建1 个 Servlet 实例
- 后续所有请求:都由新线程调用这个实例的方法
- 服务器关闭:销毁实例
线程安全的根源
- 局部变量:线程安全(每个线程独立栈内存,互不干扰)
- 实例变量 / 静态变量:非线程安全(所有线程共享堆内存中的同一个对象)
二、解决方案(推荐优先级从高到低)
方案 1:【最佳】使用局部变量(首选)
把共享的实例变量,改成方法内的局部变量,从根源避免共享。
public class SafeServlet extends HttpServlet { // 不定义实例变量 @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { // 局部变量:线程安全,每个线程独立拥有 String userName = request.getParameter("name"); try { Thread.sleep(1000); response.getWriter().write("用户:" + userName); } catch (Exception e) { e.printStackTrace(); } } }优点:无锁、高性能、代码简洁,是 Servlet 开发标准规范。
方案 2:使用 ThreadLocal 存储线程私有数据
如果必须用实例变量,用ThreadLocal实现线程隔离,每个线程只操作自己的数据。
public class ThreadLocalServlet extends HttpServlet { // 线程本地变量:每个线程独立副本 private ThreadLocal<String> userLocal = new ThreadLocal<>(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { // 存入当前线程的数据 userLocal.set(request.getParameter("name")); Thread.sleep(1000); // 获取当前线程的数据,互不干扰 response.getWriter().write("用户:" + userLocal.get()); } finally { // 必须手动清除,防止内存泄漏 userLocal.remove(); } } }方案 3:同步代码块(慎用,会降低性能)
使用synchronized加锁,让代码串行执行,但会阻塞并发请求,高并发下性能极差。
public class SyncServlet extends HttpServlet { private String userName; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { // 加锁:同一时间只有一个线程执行 synchronized (this) { userName = request.getParameter("name"); try { Thread.sleep(1000); response.getWriter().write("用户:" + userName); } catch (Exception e) { e.printStackTrace(); } } } }三、哪些对象是线程安全 / 不安全的?
| 对象 | 作用域 | 线程安全 | 说明 |
|---|---|---|---|
HttpServletRequest | 请求 | ✅ 安全 | 每个请求独立对象 |
HttpServletResponse | 请求 | ✅ 安全 | 每个请求独立对象 |
| 局部变量 | 方法 | ✅ 安全 | 线程私有 |
| Servlet 实例变量 | 应用 | ❌ 不安全 | 多线程共享 |
| 静态变量 | 应用 | ❌ 不安全 | 全局共享 |
ServletContext | 应用 | ❌ 不安全 | 全局共享,需要加锁 |
HttpSession | 会话 | ❌ 不安全 | 同一用户多请求共享 |
request.getParameter()和getAttribute()的区别?
一、核心本质区别
getParameter()获取浏览器提交的请求参数,是客户端 → 服务器传来的数据,只读、字符串类型、天生跨请求传输。getAttribute()获取服务端域对象中手动存入的属性数据,是服务器内部自己存、自己取的数据,可增删改、任意类型,仅在当前域范围内有效。
二、全方位对比
1. 数据来源
getParameter()来自:表单、URL 拼接参数、ajax 请求参数、地址栏?key=valuegetAttribute()来自:服务端代码手动调用request.setAttribute(key,value)存入
2. 数据类型
getParameter():永远返回 String,多参数数组用getParameterValues()getAttribute():返回 Object,需要手动强转(String、集合、实体类对象等)
3. 可读写性
getParameter():只能获取,不能修改 / 设置,无setParameter()方法getAttribute():可读可写,搭配setAttribute()/removeAttribute()使用
4. 生命周期 & 作用范围
getParameter()只针对单次请求,请求结束参数自动销毁,天然跟随一次请求。getAttribute()数据绑定在request域对象中,一次请求、多次转发间共享;重定向会产生新 request,属性直接丢失。
5. 使用场景
getParameter()接收用户输入:账号、密码、查询条件、表单提交数据。
getAttribute()服务端数据流转:Servlet 查询数据 → 存入 request → 转发到 JSP / 其他 Servlet 展示。
三、转发 & 重定向 关键区别(必考)
请求转发(forward)一次请求,
request对象不变: 参数保留、getAttribute()数据保留重定向(redirect)两次请求,新建
request: 原有 parameter 无保留、getAttribute()数据全部丢失