Spring中的线程安全

Spring作为一个IOC/DI容器,帮助我们管理了许许多多的 “bean”。但是,Spring并没有保证这些对象的线程安全,需要开发者自己编写线程安全问题的代码。

Spring对每个bean提供了一个scope属性来便是该bean的作用域。它是bean的生命周期。例如,一个scope为singleton的bean,在第一次被注入时,会创建一个单例对象,该对象会一直被复用到应用结束。

  • singleton:默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring
    IOC容器一致的(但在第一次被注入时才会创建)。

  • prototype:bean被定义为在每次注入时都会创建一个新的对象。

  • request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。

  • session:bean被定义为在一个session的生命周期内创建一个单例对象。

  • application:bean被定义为在ServletContext的生命周期中复用一个单例对象。

  • websocket:bean被定义为在websocket的生命周期中复用一个单例对象。

无状态Bean:就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

有状态Bean:就是有实例变量的对象,可以保存数据,是非线程安全的。

我们交由Spring管理的大多数对象其实都是一些无状态的对象,这种不会因为多线程而导致状态被破坏的对象很适合Spring的默认scope,每个单例的无状态对象都是线程安全的(也可以说只要是无状态的对象,不管单例多例都是线程安全的,不过单例毕竟节省了不断创建对象与GC的开销)。无状态的对象即是自身没有状态的对象,自然也就不会因为多个线程的交替调度而破坏自身状态导致线程安全问题

Spring存在线程安全问题的根本原因:成员变量(静态成员变量 / 成员变量)

场景一:静态成员变量
静态变量(用static修饰)即类变量,位于方法区,为所有对象共享,共享一份内存。不管是单例(singleton)还是多例(prototype)模式下,如果是只读就是线程安全的,一旦静态变量被修改,其他对象均对修改可见,就非线程安全了。所以尽量不要在@Controller/@Service等容器中定义静态变量,一旦涉及到变量被修改,不论是单例(singleton)还是多实例(prototype)他都是线程不安全的。

场景二:静态成员常量
静态常量,用static final修饰。
是线程安全。

场景三:成员变量
实例变量为对象实例私有,在虚拟机的堆中分配,单例模式(若在系统中只存在一个此对象的实例),在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故非线程安全;多例模式(如果每个线程执行都是在不同的对象中),那对象与对象之间的实例变量的修改将互不影响,故线程安全。

场景四:局部变量
每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题,都是安全的。

场景五:实体对象
实体,如:DO、DTO、VO等,这些每次都会创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题

场景六:Controller、Service、Dao
非线程安全的,因为可能存在成员变量(静态成员变量 / 成员变量)

场景七:@Resource 、@Autowired引入
线程安全的,因为Spring会引入ThreadLocal

解决方案
(1)不要在bean中声明任何有状态的实例变量或类(静态)变量

(2)如果必须设置成员变量,可以使用ThreadLocal把变量变为线程私有的

(3)如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。

(4)类上添加@Scope注解的属性改一下改成多实例的:@Scope(value = "prototype")

你可能感兴趣的:(spring,安全,java)