默认情况下,Spring Boot 中的 Bean 是非线程安全的。这是因为,默认情况下 Bean 的作用域是单例模式,那么此时,所有的请求都会共享同一个 Bean 实例,这意味着这个 Bean 实例,在多线程下可能被同时修改,那么此时它就会出现线程安全问题。
并不是,单例 Bean 分为以下两种类型:
所以说: 有状态的单例 Bean 是非线程安全的,而无状态的 Bean 是线程安全的 。
无状态的 Bean 指的是不存在成员变量,或只有查询操作,没有修改操作,它的实现示例代码如下:
import org.springframework.stereotype.Service;@Servicepublic class StatelessService {public void doSomeTask() {// 执行任务}}
有成员变量,并且存在对成员变量的修改操作,如下代码所示:
import org.springframework.stereotype.Service;@Servicepublic class UserService {private int count = 0;public void incrementCount() {count++; // 非原子操作,并发存在线程安全问题}public int getCount() {return count;}}
想要保证有状态 Bean 的线程安全,可以从以下几个方面来实现:
具体实现如下。
实现代码如下:
import org.springframework.stereotype.Service;@Servicepublic class UserService {private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);public void incrementCount() {count.set(count.get() + 1);}public int getCount() {return count.get();}}
使用 ThreadLocal 需要注意一个问题,在用完之后记得调用 ThreadLocal 的 remove 方法,不然会发生内存泄漏问题。
锁机制中最简单的是使用 synchronized 修饰方法,让多线程执行此方法时排队执行,这样就不会有线程安全问题了,如下代码所示:
import org.springframework.stereotype.Service;@Servicepublic class UserService {private int count = 0;public synchronized void incrementCount() {count++; // 非原子操作,并发存在线程安全问题}public int getCount() {return count;}}
(3)设置为原型作用域
原型作用域通过 @Scope("prototype") 来设置,表示每次请求时都会生成一个新对象(也就没有线程安全问题了),如下代码所示:
import org.springframework.stereotype.Service;@Service@Scope("prototype")public class UserService {private int count = 0;public void incrementCount() {count++; // 非原子操作,并发存在线程安全问题}public int getCount() {return count;}}
(4)使用线程安全容器
我们可以使用线程安全的容器,例如 AtomicInteger 来替代 int,从而保证线程安全,如下代码所示:
import org.springframework.stereotype.Service;import java.util.concurrent.atomic.AtomicInteger;@Servicepublic class UserService {private AtomicInteger count = new AtomicInteger(0);public void incrementCount() {count.incrementAndGet();}public int getCount() {return count.get();}}
实际工作中如何保证线程安全?
实际工作中,通常会根据具体的业务场景来选择合适的线程安全方案,但是以上解决线程安全的方案中,ThreadLocal 和原型作用域会使用更多的资源,占用更多的空间来保证线程安全,所以在使用时通常不会作为最佳考虑方案。
而锁机制和线程安全的容器通常会优先考虑,但需要注意的是 AtomicInteger 底层是乐观锁 CAS 实现的,因此它存在乐观锁的典型问题 ABA 问题(如果有状态的 Bean 中既有 ++ 操作,又有 -- 操作时,可能会出现 ABA 问题),此时就要使用锁机制,或 AtomicStampedReference 来解决 ABA 问题了。
单例模式的 Bean 并不一定都是非线程安全的,其中有状态的 Bean 是存在线程安全问题的。实际工作中通常会使用锁机制(synchronized 或 ReentrantLock)或线程安全的容器来解决 Bean 的线程安全问题,但具体使用哪种方案,还要结合具体业务场景来定。