Java面试基础
2026/4/29 3:06:20 网站建设 项目流程

Servlet的多线程同步问题

servlet 是单实例多线程的工作模式:服务器只会为每个 Servlet 创建一个实例对象,所有客户端请求都会复用这个实例,由多个线程并发调用service()/doGet()/doPost()方法处理请求。

这就带来了多线程同步问题:如果 Servlet 中定义了实例变量,多个线程同时读写会引发数据错乱、线程安全问题。

一、核心原理:为什么会有线程安全问题?

  1. Servlet 生命周期

    • 服务器启动 / 第一次访问时:创建1 个 Servlet 实例
    • 后续所有请求:都由新线程调用这个实例的方法
    • 服务器关闭:销毁实例
  2. 线程安全的根源

    • 局部变量:线程安全(每个线程独立栈内存,互不干扰)
    • 实例变量 / 静态变量非线程安全(所有线程共享堆内存中的同一个对象)

二、解决方案(推荐优先级从高到低)

方案 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()的区别?

一、核心本质区别

  1. getParameter()获取浏览器提交的请求参数,是客户端 → 服务器传来的数据,只读、字符串类型、天生跨请求传输

  2. getAttribute()获取服务端域对象中手动存入的属性数据,是服务器内部自己存、自己取的数据,可增删改、任意类型,仅在当前域范围内有效。

二、全方位对比

1. 数据来源

  • getParameter()来自:表单、URL 拼接参数、ajax 请求参数、地址栏?key=value

  • getAttribute()来自:服务端代码手动调用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()数据全部丢失

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询