品牌型号:联想ThinkPad X1
系统:Windows10家庭版
软件版本:Spring 5.3.7
Spring的单例Bean几乎每个项目都在用,但真正想过线程安全问题的人并不多,我们平时写代码跑起来没问题,一旦并发访问,就会出现数据异常,排查半天才发现是Bean里的某个成员变量被多个线程同时修改了。这类问题不是偶发,而是单例本身的特性决定的,同一个实例在多个线程间共享,稍不注意就容易踩坑。下面就给大家介绍一下Spring单例模式怎么保证线程安全,Spring如何解决单例线程安全问题的相关内容。
一、Spring单例模式怎么保证线程安全
说一个很多开发者都没意识到的问题,Spring本身并不负责保证单例Bean的线程安全,这件事需要我们开发者自己来处理。框架只管把Bean创建出来、放到容器里、需要的时候注入,至于这个Bean在多线程环境下安不安全,Spring是不管的。

那为什么平时我们写的Service、Dao单例用着没出问题?这在于这些类通常是无状态的,也就是说类里面没有会被修改的成员变量,方法执行依赖的数据都来自参数或者局部变量。局部变量存在线程栈上,每个线程独立一份,不共享,也就不存在竞争问题。不是Spring保证了线程安全,是代码结构本身规避了风险。

容易出问题的是单例Bean里定义了可变的实例变量,并且多个线程会同时读写它。举个例子:Bean里有一个count字段,每次请求进来都对它做累加,这种写法在并发下必然出错。 可以从设计上避免在单例Bean里存状态,把状态下沉到方法参数或者ThreadLocal里,或者是对共享变量加同步控制,比如用synchronized或者AtomicInteger这类并发工具类。如果这个Bean确实需要独立状态,就改成多例,每次注入都拿到新实例。

二、Spring如何解决单例线程安全问题
Spring本身不负责单例的线程安全,但这不代表框架层面什么都没做,它在某些地方其实有自己的处理方式,只是很多人没留意过。
比方说ThreadLocal的使用,Spring在处理事务、请求上下文这类场景时,内部大量用到了ThreadLocal来隔离线程间的数据,每个线程往ThreadLocal里存自己的数据,互相看不到,自然也不会互相干扰。

除了ThreadLocal,还有个常见手段就是把可变状态从Bean里剥离出去,单例Bean只负责逻辑,不存数据,需要的数据通过方法参数传入,处理完直接返回,Bean本身始终是无状态的。这种做法改动成本低,也是日常开发中很推荐的方式。大多数线程安全问题,都可以靠“让Bean无状态”来规避,不需要引入额外的同步机制。

有些场景确实避不开共享变量,这时候就得上同步手段了,可以直接用synchronized,不过粒度粗、性能损耗大,高并发下要谨慎使用。更常见的做法是用java.util.concurrent包里的工具类,比如计数用AtomicInteger,需要线程安全的集合就用ConcurrentHashMap,这些工具在保证安全的同时性能要好得多。

以上就是Spring单例模式怎么保证线程安全,Spring如何解决单例线程安全问题的全部内容了。单例的线程安全问题不在Spring,在于我们代码有没有在Bean里存可变状态。Spring本身可以通过ThreadLocal隔离了框架层面的数据,但业务代码这块得我们把关。可以优先考虑让Bean无状态,把数据交给方法参数流转,其次再考虑用ThreadLocal隔离线程数据。如果真需要共享变量,就考虑AtomicInteger、ConcurrentHashMap这类并发工具,慎用粗粒度的synchronized。