大连密封材料 发表于 7 天前

Servlet 线程安全与并发编程深度解析

Servlet 线程安全与并发编程深度解析

一、Servlet 线程安全机制与风险场景

1.1 Servlet 容器工作原理



[*]单实例多线程模型:每个Servlet在容器中只有一个实例,通过线程池处置惩罚并发哀求
[*]哀求处置惩罚流程:
[*]吸收HTTP哀求创建HttpServletRequest和HttpServletResponse
[*]从线程池获取工作线程
[*]调用service()方法处置惩罚哀求
[*]返反相应后线程归还线程池

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";                                  // 编译错误


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Servlet 线程安全与并发编程深度解析