从 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 的设计哲学,面试官立刻觉得你"格局打开了"。

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

相关推荐
呆呆敲代码的小Y5 小时前
【游戏开发】C# 中的迭代器
java·开发语言·c#·迭代器
TinyMemory5 小时前
Java 面向对象核心入门(六):this 关键字完全解析
java·笔记·面向对象·java新手·this关键字
萧瑟余晖5 小时前
Java深入解析篇九之JavaStream API详解
java
一只QAQ5 小时前
c++项目
java·c++·算法
小蒜学长5 小时前
基于SpringBoot + Vue的智能健身房管理系统的设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
DolitD5 小时前
云流技术深度剖析:单服务器下如何实现3D应用的多实例并发?
java·服务器·前端·3d·云原生·云计算
jun_bai6 小时前
Ecplise中发布项目到配置的tomcat中
java·服务器
liangbo76 小时前
JVM规范第 6 章:Java 虚拟机指令集
java·jvm
南城以南溫暖如初1476 小时前
AI 智能商城 APP 定制开发的核心需求
java·人工智能·spring boot·mysql·mybatis·需求分析
摇滚侠6 小时前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 21
java·spring boot·笔记