Java并发编程——Spring的线程安全

一、Spring没有保证bean的线程安全

1.线程安全问题的由来:

  • 线程安全问题都是由全局变量及静态变量引起的
  • 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;
  • 若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

2.spring不保证bean的线程安全

Spring没有保证这些bean的线程安全,需要由开发者自己编写解决线程安全问题的代码。

spring中的bean默认都是单例的,ioc容器中一个类只会存在一个实例对象。bean存在着两种分类:无状态bean和有状态bean

2.1 无状态bean线程安全的原因:

  • spring中,绝大部分bean都是无状态的,因此即使这些bean默认是单例的,也不会出现线程安全问题的。
  • spring项目中的controllerservicedao这些类,这它们里面通常不会含有成员变量,因此它们被设计成单例的。如果这些类中定义了实例变量,就线程不安全了,所以尽量避免定义实例变量;
也可以说只要是无状态的对象,不管单例多例都是线程安全的,不过单例毕竟节省了不断创建对象与GC的开销。

2.2有状态bean线程安全的方法:

对于spring中有状态的bean,比如RequestContextHolderTransactionSynchronizationManagerLocaleContextHolder

  • 如果bean中声明了有状态的实例变量或类变量,那么该bean就成了有状态的bean,就使用ThreadLocal把变量变为线程私有的;
  • 如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronizedlockCAS等这些实现线程同步的方法了;
  • 对于有状态的bean,也可以使用原型模式(prototype),每次使用时都会重新生成一个对象,解决了线程不安全的问题;

3.Springbean的作用域

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

3.1bean的作用域(scope)取值:

  • singleton:默认的scope,每个scopesingletonbean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。在整个Spring IoC容器里,只有一个bean实例,所有线程共享该实例。
  • prototypebean被定义为在每次注入时都会创建一个新的对象。每次请求都会创建并返回一个新的实例,所有线程都有单独的实例使用,这种方式是比较安全的,但会消耗大量内存和计算资源。(在有线程安全需求的情况下,可以使用)
  • request(请求范围实例):bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。每当接受到一个HTTP请求时,就分配一个唯一实例,这个实例在整个请求周期都是唯一的。
  • session(会话范围实例):bean被定义为在一个session的生命周期内创建一个单例对象。在每个用户会话周期内,分配一个实例,这个实例在整个会话周期都是唯一的,所有同一会话范围的请求都会共享该实例。
  • applicationbean被定义为在ServletContext的生命周期中复用一个单例对象。
  • websocketbean被定义为在websocket的生命周期中复用一个单例对象。
  • globalsession(全局会话范围实例):这与会话范围实例大部分情况是一样的,只是在使用到portlet时,由于每个portlet都有自己的会话,如果一个页面中有多个portlet而需要共享一个bean时,才会用到。

有人可能会认为,我使用request作用域不就可以避免每个请求之间的安全问题了吗?这是完全错误的,因为Controller默认是单例的,一个HTTP请求是会被多个线程执行的,这就又回到了线程的安全问题。当然,你也可以把Controller的scope改成prototype,实际上Struts2就是这么做的,但有一点要注意,Spring MVC对请求的拦截粒度是基于每个方法的,而 Struts2是基于每个类的,所以把Controller设为多例将会频繁的创建与回收对象,严重影响到了性能

4.spring中的无状态对象

  • 无状态的对象即是自身没有状态的对象,自然也就不会因为多个线程的交替调度而破坏自身状态导致线程安全问题。
  • 无状态对象包括我们经常使用的DODTOVO这些只作为数据的实体模型的对象,还有ServiceDAOController,这些对象并没有自己的状态,它们只是用来执行某些操作的。
  • 例如,每个DAO提供的函数都只是对数据库的CRUD,而且每个数据库Connection都作为函数的局部变量(局部变量是在用户栈中的,而且用户栈本身就是线程私有的内存区域,所以不存在线程安全问题),用完即关(或交还给连接池)。

Java并发编程学习系列

如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)


版权声明:本文为zjt980452483原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。