Spring Boot如何处理多并发请求?单例Bean的类变量是否线程安全?
单例Bean并发问题解答
示例代码的执行结果
你给出的SampleBean如果使用Spring默认的单例作用域,user2进入dummyFunction时拿到的dummyVariable一定是user1修改后的值,不可能是初始的null。
原因非常明确:Spring单例模式下,整个IOC容器只会创建一个SampleBean实例,类的成员变量归实例所有,存储在堆内存中,被所有请求线程共享。你这段代码没有加任何同步控制,多线程并发调用时必然出现线程安全问题,甚至会抛出空指针异常:比如user1刚执行完dummyVariable = null,user2刚好执行到dummyVariable += 10,拆箱操作就会触发NPE。
Spring Boot多并发请求处理逻辑
Spring Boot本身不直接处理请求,底层依赖Tomcat、Jetty等Servlet容器完成请求分发,核心逻辑如下:
- Servlet容器会维护一个可配置大小的线程池,每收到一个用户请求,就从线程池分配一个空闲线程来处理该请求,不同请求对应独立的线程,请求处理完成后线程归还线程池复用。
- Spring默认将Controller、Service、Repository等Bean都注册为单例,之所以可以用单例承接高并发,核心前提是Bean中没有可变的成员变量:方法内的局部变量存储在每个线程自己的虚拟机栈中,是线程私有的,不同线程调用同一个方法时,局部变量互不影响,不存在线程安全问题,同时单例省去了反复创建销毁对象的开销,支撑高并发场景。
单例Bean线程安全问题的解决方案
如果业务确实需要在Bean中维护可变状态,可以选择以下方案:
- 调整Bean的作用域为
prototype:每次请求注入Bean时都会创建新的实例,线程之间不共享实例,自然没有线程安全问题,缺点是频繁创建对象开销大,高并发场景不推荐。 - 用
ThreadLocal存储可变变量:每个线程会持有变量的独立副本,线程之间互不干扰,使用完要手动调用remove()方法避免内存泄漏。 - 对成员变量的操作加同步锁:使用
synchronized关键字或者Lock接口控制变量的读写,缺点是加锁会降低并发吞吐量,仅适合低并发场景。 - 最推荐的方案:尽量避免在Bean中定义可变成员变量,所有业务状态都通过方法局部变量或者参数传递,从根源上避免线程安全问题。
Spring Boot并发核心知识点梳理
要完整理解整套逻辑,重点掌握以下知识点即可:
- Spring Bean的6种作用域:singleton、prototype、request、session、application、websocket的定义和适用场景
- Java内存模型:堆内存线程共享、虚拟机栈线程私有,成员变量和局部变量的存储位置差异
- Servlet容器的线程池模型:请求分发逻辑、线程复用机制、核心参数配置规则
- Java多线程基础:竞态条件、同步机制、ThreadLocal实现原理
内容的提问来源于stack exchange,提问作者Java Programmer
相关产品推荐
相关产品推荐

