从 IOC 到多线程:Spring 单例 Bean 的并发安全性全解析

原文来自于:zha-ge.cn/java/103

从 IOC 到多线程:Spring 单例 Bean 的并发安全性全解析

春天到了,Spring 的故事又要开讲。你有没有和我一样,第一次用 Spring 的时候,有点怵单例 Bean?心里总嘀咕:单例不会有并发问题吗?要是两个线程一起用同一个 Bean,不就打架了吗?结果看了三天源码,差点没在 XML 配置里睡着。哎,踩坑的路,就是这么波澜不惊地温柔。

那些年我追过的单例 Bean

单例在 Spring 里啥都好用,写起来不用操心,各种自动装配,@Autowired 一打,配好就行。配置里 scope="singleton",不写其实也是 singleton,Spring 就帮咱省事。说人话就是:

  • 全容器只有一个这个 Bean 对象
  • 不管注入几次,用的其实就那一个
  • Bean 默认是线程安全的?

你品一品,线程安全?真的安全吗?我那会儿还信了......

那天阳光很好,我和并发聊了一会天

场景:有个计数器 Bean,里头就一个 int 计数。然后两个线程一起操作,大家都懂得:

java 复制代码
@Component
public class Counter {
    private int count = 0;
    public void increment() { count++; }
    public int get() { return count; }
}

没啥花活,就是纯粹为了让坑掉出来。线程一边加,线程一边读,我抱着"Spring 会帮我做好"的想法,放心地写了下面这段测试:

java 复制代码
@Autowired
private Counter counter;
// 两个线程轮流 counter.increment()

没过两分钟,count 就不对了。这是"你有你的张良计,Spring 有它的背水一战"------原来单例 Bean 并不是线程安全的!

踩坑瞬间

正式进大坑时,心情是这样的------

  • 以为 Spring 单例 Bean 是万能的,能包治百病,结果它只是全局唯一
  • 想用加锁,纠结死------好像丑了点,还憋屈
  • 问 ChatGPT(没错,就是你),它说"用 volatile 或者原子类"
  • 菜鸟我试了下 AtomicInteger,哎,还真好使!

这波下来,我悟了:Spring 只保证 Bean 是单例,不包线程安全。咱但凡 Bean 里有"状态"(尤其是可变字段),多线程就可能捅漏子。别问为啥,单例就是一家公司只有一个马云,几个员工抢话筒谁说了算?

贴个关键代码,妙用 Java 并发:

java 复制代码
@Component
public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() { count.incrementAndGet(); }
    public int get() { return count.get(); }
}

这样就再也不怕多线程抓头发了。

经验启示

Spring 单例只管"唯一",不管"安全" 单例的本质,是容器里只放一份对象;线程安全性得靠你自己兜底。

无状态服务天然安全 如果方法里只处理输入参数,不依赖成员变量,也不修改 Bean 的内部字段,那么这个 Bean 再多人抢着用都没问题。典型的就是各种工具类、DAO 层的接口代理。

有状态 Bean 要小心 一旦有可变成员变量(比如计数器、缓存、上下文记录),并发读写就有可能出错。要么加锁、要么用并发包里的原子类(AtomicInteger、ConcurrentHashMap 等)。

实在绕不开共享状态 那就乖乖用 synchronized、Lock、ThreadLocal 之类的手段来隔离或者保护。比如用户上下文就常用 ThreadLocal 来保证每个线程拿到自己的副本。

面试官杀手锏问题

问:Spring 的单例 Bean 是线程安全的吗?

答法:

Spring 单例只保证全局唯一实例,不自动保证线程安全。 如果 Bean 是无状态的(纯粹的方法调用),线程安全。 如果 Bean 内部持有可变状态,就需要开发者自己保证安全。 常用手段有原子类、并发容器、ThreadLocal,或者直接把 Bean 设计为无状态。 这么答,既简洁又能点出关键,面试官大概率会满意。

写在最后

Spring 单例 Bean 的并发安全性,说白了就是一句话: Spring 管你"有没有",不管你"安不安全"。

想偷懒:尽量写无状态 Bean。 想稳妥:用并发工具类兜底。 想装逼:顺手再抛出 IOC + AOP 的设计哲学,面试官立刻觉得你"格局打开了"。

等你踩过一次"计数器翻车"的坑,就再也不会天真地以为"单例=线程安全"了。

相关推荐
Sweet锦6 分钟前
jDCS 开源项目:面向工业现场的 Modbus RTU 数据采集基础框架
java·spring boot·物联网·开源
海马7 分钟前
Spring Boot 开发知识整理
java·spring boot·后端
xiaolinudao1238 分钟前
将多个 Excel 表格中的数据合并到单个表中|6 种实现方案全解析
java·前端·excel
谢亮_vipxieliang14 分钟前
Spring Boot 3.x 从零开始——环境搭建与第一个 REST 项目
java·spring boot·后端
Wang's Blog23 分钟前
Java 项目部署之 Docker工具快速入门: Docker 架构拆解:镜像、容器、守护进程与 Registry
java·docker·架构
栗子~~24 分钟前
SpringCloud Gateway 基于 Nacos 实现动态路由
java·spring cloud·gateway
FYKJ_201031 分钟前
django个性化新闻推荐35173-计算机课程设计、毕业设计
java·spring boot·后端·python·架构·django·课程设计
计算机毕设定制辅导-无忧学长41 分钟前
《校园食堂在线订餐小程序的设计与实现》
java·vue.js·spring boot·微信小程序·uniapp·校园食堂在线订餐小程序
夜之眷属1 小时前
服务启动即崩:InetAddress.getLocalHost() 抛 UnknownHostException 排查记
java·运维·后端·docker
IT研究室1 小时前
最新计算机毕业设计选题推荐-基于spring boot的食品安全信息管理系统-网站-文档指导-Java-springboot
java·spring boot·课程设计