Spring Bean 中的线程安全

在使用Spring框架时,很多时候不知道或者忽视了多线程的问题。因为写程序时,或做单元测试时,很难有机会碰到多线程的问题,因为没有那么容易模拟多线程测试的环境。但如果不去考虑潜在的漏洞,它就会变成程序的隐形杀手,在你不知道的时候爆发。而且,通常是程序交付使用时,在生产环境下触发,会是很麻烦的事。

那么Spring Bean在大多数情况下,对象实例(Object)和方法是否线程安全呢?

这就要看如何定义Bean的生命管理和范围(Scope)了,就大多数情况而言,如果不特别注意并采取方法,Spring Bean是非线程安全的。原因是Spring Bean的产生是通过容器实现,而使用反向控制IoC时,注入的对象实例也是与其他线程共享的。

例如

public class PersonController{    
    private PersonService personService;    
    
    public void setFirstName(HttpServletRequest request){                    
        personService.setFirstName(request.getParameter("firstname"));    
    }    
    
    public String getFirstName(){             
        return   personService.getFirstName();    
    }
                 
}

当有两个线程访问PersonController时,一个先调用setFristName来设置firstname, 而另一个线程调用getFirstName就会得到第一个线程所设的值。这是因为,PersonController是缺省情况下为单一实例(Singleton),而personService也是一个单独的实例被注入到personController里。这种情况下,多线程是不安全的,除非能够保证每个方法(method)都不会改变所共享的对象的属性或状态。

线程安全的方法

public class PersonController {   
 
    private PersonService personService
    
    public void setFirstName(HttpServletRequest request){        
      Person person = new Person();        
      person.setFirstName();        
      personService.savePerson(person);    
    }
 }

上面的例子是一种线程安全的写法,例如,线程A访问setFirstName方法,当它执行到person.setFirstName()后,系统决定挂起这个线程,转向执行B,B做了同样的事情并保存。然后,系统再执行被挂起的A线程,由于A线程有自己的堆栈状态,而且它本身没有与其他线程共享的对象实例或状态。需要补充说一下,这里假定personService.savePerson()方法是线程安全的,否则,还需要调查personService.savePerson()是否线程安全。

因此,在大多数情况下,spring bean是非线程安全的,或者说,如果你不告诉它如何管理对象或方法的线程安全,那么就会潜在线程安全问题。

spring bean因此给出了以下的线程安全的可用声明(annotation),你可以根据实际情况,分别定义你的类或方法。

单实例 singleton(缺省)

在整个Spring IoC容器里,只有一个bean实例,所有线程共享该实例

原型实例prototype

每次请求都会创建并返回一个新的实例,所有线程都有单独的实例使用,这种方式是比较安全的,但会消耗大量内存和计算资源。

定义bean的xml配置文件

<bean id="personService" class="com.test.PersonService" scope="prototype">    
<!-- inject dependencies here as required -->
</bean>
<bean id="personControoler" class="com.test.PersonController">    
    <lookup-method name="getPersonService" bean="personService"/>
</bean>

抽象类

public abstract class PersonController {    
    public void setFirstName(HttpServletRequest request){        
        Person person = new Person();        
        person.setFirstName();        
        personService.savePerson(person);    
     }   
     
     public abstract PersonService getPersonService();
 }

这里,每次当类中的getPersonService()方法被调用时,就会得到一个新personService实例。需要注意的是,PersonController变成了抽象类。

请求范围实例request

每当接受到一个HTTP请求时,就分配一个唯一实例,这个实例在整个请求周期都是唯一的。

会话范围实例session

在每个用户会话周期内,分配一个实例,这个实例在整个会话周期都是唯一的,所有同一会话范围的请求都会共享该实例。

全局话范围实例globalsession

这与会话范围实例大部分情况是一样的,只是在使用到portlet时,由于每个portlet都有自己的会话,如果一个页面中有多个portlet而需要共享一个bean时,才会用到。

Request范围和Session范围在高并发性的MVC环境下的使用

从上面得知,在缺省情况下,被添加上声明@Controller的类是单一实例的,因此,你需要特别留意类中的方法与变量的线程安全问题。

而当考虑到并发用户的环境下,有些时候可以使用Reqest范围,例如,你需要为每次用户的请求分配一个新的控制器实例,这通常是面对一些处理逻辑很复杂的请求,需要占用比较多的资源以及反应时间要求较高的情况,比如,网上订票或支付。

@[email protected]("request")
public class OnlinePaymentController {                 
    private OnlinePayment payment;          
    public void pay(Float amount){                    
        creditCheck(user);                    
        payment.pay(amount);                    
        ...                    
        //a lot of processes have to be done here          
    }
}

有些时候,可以考虑使用Session范围,针对每个用户,提供一个实例,应付用户的多次请求,例如,网上购物车。

@Controller
@Scope("session")
public class ShoppingCartController {       

          private ShoppingCart shoppingCart

          public void checkIn(ShoppingItem item, Integer number){                    
              shoppingCart.add(item, number);          
          }          

          public void checkOut(){                    
              shoppingCart.sum();          
          }
 }

实际生产环境中用的比较多的是单一实例与Session范围,如果能够保证单一实例中的线程安全,那么在性能上是首选的,毕竟,当网站用户量不断增加时,仍然能够用有限的实例来处理多个用户请求是比较划算的。

时间: 2024-10-12 23:15:21

Spring Bean 中的线程安全的相关文章

Spring bean中的properties元素内的name 和 ref都代表什么意思啊?

<bean id="userAction" class="com.neusoft.gmsbs.gms.user.action.UserAction" scope="prototype"> <property name="userBO" ref="userBO" /> </bean> Spring bean中的properties元素内的name 和 ref都代表什么意思啊

spring bean中的properties元素内的ref和value的区别;* 和 ** 的区别

spring bean中的properties元素内的ref和value的区别 至于使用哪个是依据你所用的属性类型决定的. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property na

复现一个典型的线上Spring Bean对象的线程安全问题(附三种解决办法)

问题复现 假设线上是一个典型的Spring Boot Web项目,某一块业务的处理逻辑为: 接受一个name字符串参数,然后将该值赋予给一个注入的bean对象,修改bean对象的name属性后再返回,期间我们用了 Thread.sleep(300) 来模拟线上的高耗时业务 代码如下: @RestController @RequestMapping("name") public class NameController { @Autowired private NameService n

Spring bean中id和name的区别

最近在开发项目的时候发现spring的BeanFactory创建bean的时候有时候有2种不同的配置,即<bean id="test" class="com.TestImpl">与<bean name="test" class="com.TestImpl">,下面一起分析一下这2种配置的区别: 1.命名规范.id属性命名必须满足xml命名规范,而name属性命名可以不用遵守.不论是id属性还是name属

spring bean中构造函数,afterPropertiesSet和init-method的执行顺序

http://blog.csdn.net/super_ccc/article/details/50728529 1.xml文件 [html] view plain copy print? <bean id="aaa" class="com.dingwang.Test.Aaa" init-method="init"> <constructor-arg name="name" value="ddd&qu

一道78%的Java程序员搞不清的Spring bean面试题

熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton.prototype.request.session.global session.如下图是官方文档上的截图,感兴趣的朋友可以进去看看这五种分别有什么不同.今天要介绍的是这五种中的前两种,也是Spring最初提供的bean scope singleton 和 prototype.Spring官方文档介绍如下图: 单例bean与原型bean的区别 如果一个bean被声明为单例的时候,在处理多次请求的时候在Spr

Spring Bean生命周期详解

对象生命周期:创建(实例化----初始化)---使用----销毁,而在Spring中,Bean对象周期当然遵从这一过程,但是Spring提供了许多对外接口,允许开发者对三个过程(实例化.初始化.销毁)的前后做一些操作.在Spring Bean中,实例化是为Bean对象开辟空间(构造函数),初始化则是对属性的初始化,属性注入(setter方法注入属性). 1.Bean自身方法:init-method/destroy-method,通过为配置文件bean定义中添加相应属性指定相应执行方法. 2.Be

Spring中的线程池ThreadPoolTaskExecutor

1.直接调用Spring框架中的ThreadPoolTaskExecutor ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor(); //线程池所使用的缓冲队列 poolTaskExecutor.setQueueCapacity(200); //线程池维护线程的最少数量 poolTaskExecutor.setCorePoolSize(5); //线程池维护线程的最大数量 poolTaskExecutor.s

【Spring】8、Spring框架中的单例Beans是线程安全的么

看到这样一个问题:spring框架中的单例Beans是线程安全的么? Spring框架并没有对单例bean进行任何多线程的封装处理.关于单例bean的线程安全和并发问题需要开发者自行去搞定.但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的.如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全. 最浅显的解决办法就是将多态bean的作用域由"singleton&