Spring单例Bean和线程安全 - duanxz - 博客园

标签: | 发表时间:2021-06-21 09:18 | 作者:
出处:https://www.cnblogs.com

Spring的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢?本文介绍了以上的安全问题。

Spring的原型Bean与单例Bean的设置

spring单例Bean

spring中的Bean缺省的情况下是单例模式的,在 spring容器中分配Bean的时候(无论通过getBean()还是通过依赖注入(IOC)),它总是返回同一个Bean的实例,如果你想每次向上下文请求一个bean的时候总是得到一个不同的实例,或者想每次想从 spring容器中得到一个bean的不同实例,需要将bean定义为原型模式,定义为原型模式意味着你是定义一个bean的类,而不是一个单一的bean的实例,bean的实例都是按照这个类而创建的。

spring原型Bean
在spring中<bean>的singleton属性告诉上下文这个bean是原型bean或者是单例bean。bean的缺省值为 true,如果设为false的话,就把这个bean定义成了原型bean。例如:<beanid=”test” class=”demo.Demo” singleton=”false” />

在spring2.x中<bean id=”test”scope=”prototype”/>将这样配置,但是如果想使用spring的原型bean必须通过getBean(”test”)这样的方 式,而不能通过使用IOC方式,因为:getBean将每次都有spring来装配转发,而IOC将只是一次注入的目标bean中,以后不再重新注入。这 样通过getBean方式将得到一个原型bean。如果bean使用的是有限资源,如数据库和网络链接的话不需要使用原型bean,正常不要把 singleton=”false”或者scope=”prototype”除非必要。Spring使用ThreadLocal解决线程安全问题

 
  我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
 
  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。   线程安全问题都是由全局变量及静态变量引起的。 
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
1) 常量始终是线程安全的,因为只存在读操作。
2)每次调用方法前都新建一个实例是线程安全的,因为不会访问共享的资源。
3)局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量和方法内变量。
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象  ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象  .不能保存数据,是不变类,是线程安全的。
有状态对象:
无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。
Struts2默认的实现是Prototype模式。也就是每个请求都新生成一个Action实例,所以不存在线程安全问题。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。

 

Spring 单例Bean和Java 单例模式的区别

Spring的的单例是基于BeanFactory也就是spring容器,单例Bean在此Spring容器内是单个的,Java的单例是基于JVM,每个JVM内一个单例。

 

线程安全

Thread safety is a  computer programming concept applicable in thecontext of  multi-threaded programs. A piece of codeis  thread-safe if it can be safely invoked by multiple threads at thesame time  [1].

Thread safety is a key challenge in multi-threadedprogramming. It was not a concern for most application programmers of littlehome applications, but since the 1990s, as Windows became multithreaded, andwith the expansion of BSD and Linux operating systems, it has become acommonplace issue. In a multi-threaded program, several threads executesimultaneously in a shared  address space. Every thread has access to virtuallyall the  memory of every other thread. Thus the flow ofcontrol and the sequence of accesses to data often have little relation to whatwould be reasonably expected by looking at the text of the program, violatingthe  principle of least astonishment.Thread safety is a property that allows code to run in multi-threadedenvironments by re-establishing some of the correspondences between the actualflow of control and the text of the program, by means of Process synchronization.

Identification

It is not easy to determine if a piece of code isthread-safe or not. However, there are several indicators that suggest the needfor careful examination to see if it is unsafe:

 Implementation

There are a few ways to achieve thread safety:

Re-entrancy 

Writing code insuch a way that it can be partially executed by one task,  reentered by another task, and thenresumed from the original task. This requires the saving of  state information in variables local toeach task, usually on its stack, instead of in  staticor  global variables. There are still some rare caseswhere a static variable can be used in a reentrant function, if the access isdone through atomic operations.

Mutualexclusion or  Process synchronization

Access to shareddata is  serialized using mechanisms that ensure only one thread reads orwrites the shared data at any time. Great care is required if a piece of codeaccesses multiple shared pieces of data—problems include  raceconditionsdeadlocks, livelocksand  starvation.

Thread-local storage 

Variables arelocalized so that each thread has its own private copy. These variables retaintheir values across  subroutine and other code boundaries, and are thread-safesince they are local to each thread, even though the code which accesses themmight be reentrant.

Atomicoperations 

Shared data areaccessed by using  atomic operations which cannot be interrupted by otherthreads. This usually requires using special machinelanguage instructions, which might be available in a  runtimelibrary. Since the operations are atomic, the shared data are always keptin a valid state, no matter what other threads access it.  Atomicoperations form the basis of many thread locking mechanisms.

 Examples

In the following piece of  C code, the function is thread-safe, butnot reentrant

int function()
{
        mutex_lock();
        ...
        function body
        ...
        mutex_unlock();
}

In the above,  function can be called bydifferent threads without any problem. But if the function is used in areentrant interrupt handler and a second interrupt arises inside the function,the second routine will hang forever. As interrupt servicing can disable otherinterrupts, the whole system could suffer.

Concurrent programing

Note that a piece of code can be thread safe, and yet notbeing able to run at the same time that some other piece of code is running. Atrivial example of that is when that other piece of code restarts the computer.The following piece of  C code, presents a less obvious situationwhere a thread is using a file that another thread or process might delete.

int function()
{
char *filename = "/etc/config";
FILE *config;
        if (file_exist(filename)){
                config = fopen(filename);
        }
}

In the above, the function is thread-safe, as it can becalled from any number of threads and will not fail. But all the calls shouldbe in a controlled environment. If executed in a multi-process environment, orif the file is stored on a network-shared drive, there is no warranty that itwon't be deleted.

Difficulties

One approach to making data thread-safe that combinesseveral of the above elements is to make changes  atomicallyto update the shared data. Thus, most of the code is  concurrent, and little time is spentserialized.

 

 可重入函数与不可重入函数

主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。

编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。

 说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

 

示例:假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性。

unsigned int example( int para )

{

    unsigned int temp;
        Exam = para; // (**)
        temp = Square_Exam( );
        return temp;
    }
    此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋与另一个不同的para值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。

    unsigned int example( int para ) {
        unsigned int temp;
        [申请信号量操作] //(1)
        Exam = para;
        temp = Square_Exam( );
        [释放信号量操作]
        return temp;
    }
    (1)若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。

    保证函数的可重入性的方法:
    在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量),对于要使用的全局变量要加以保护(如采取关中断、信号量等方法),这样构成的函数就一定是一个可重入的函数。
    VxWorks中采取的可重入的技术有:
    * 动态堆栈变量(各子函数有自己独立的堆栈空间)
    * 受保护的全局变量和静态变量
    * 任务变量


--------------------------------------------------
    在 实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是 否会出错。不可重入函数在实时系统设计中被视为不安全函数。满足下列条件的函数多数是 可重入的:
    1) 函数体内使用了静态的数据结构;
    2) 函数体内调用了malloc()或者free()函数;
    3) 函数体内调用了标准I/O函数。

    下面举例加以说明。
    A. 可重入函数
    void strcpy(char *lpszDest, char *lpszSrc)

 {
        while(*lpszDest++=*lpszSrc++);
        *dest=0;
    }

    B. 不可重入函数1
    charcTemp;//全局变量
    void SwapChar1(char *lpcX, char *lpcY)

 {
        cTemp=*lpcX;
        *lpcX=*lpcY;
        lpcY=cTemp;//访问了全局变量
    }

    C. 不可重入函数2
    void SwapChar2(char *lpcX,char *lpcY)

 {
        static char cTemp;//静态局部变量
        cTemp=*lpcX;
        *lpcX=*lpcY;
        lpcY=cTemp;//使用了静态局部变量
    }

    问题1,如何编写可重入的函数?
    答:在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。

    问题2,如何将一个不可重入的函数改写成可重入的函数?
    答:把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。
    1) 不要使用全局变量。因为别的代码很可能覆盖这些变量值。
    2) 在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”。
    3) 不能调用其它任何不可重入的函数。
    4) 谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。

    堆栈操作涉及内存分配,稍不留神就会造成益出导致覆盖其他任务的数据,所以,请谨慎使用堆栈!最好别用!很多黑客程序就利用了这一点以便系统执行非法代码从而轻松获得系统控制权。还有一些规则,总之,时刻记住一句话:保证中断是安全的!

    实例问题:曾经设计过如下一个函数,在代码检视的时候被提醒有bug,因为这个函数是不可重入的,为什么?
    unsigned int sum_int( unsigned int base )

{
        unsigned int index;
        static unsigned int sum = 0; // 注意,是static类型
        for (index = 1; index <= base;index++)
            sum += index;
        return sum;
    }

    分析:所谓的函数是可重入的(也可以说是可预测的),即只要输入数据相同就应产生相同的输出。这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果需要一个可重入的函数,一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。
    将上面的函数修改为可重入的函数,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto类型的变量,函数即变为一个可重入的函数。
    当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

 

Spring单例的线程安全

 基本上,Spring的thread-safe是其API自身的thread-safe。比如一个常见的场景(from appfuse):
    public class UserManagerImpl extends BaseManager implements UserManager {
        private UserDao dao;
    ......
    public class UserDaoHibernate extends BaseDaoHibernate implements UserDao, UserDetailsService {
    ......
    public class BaseDaoHibernate extends HibernateDaoSupport implements Dao {
    这些bean都是Singleton的。
   一个类如果没有成员变量,那这个类肯定是thread-safe的,所以UserDaoHibernate的thread-safe取决于其父类。而 UserManagerImpl 的安全性又取决于UserDaoHibernate,最后是HibernateTemplate。可以看出,这里的bean都小心翼翼的维护其成员变量, 或者基本没有成员变量,而将thread-safe转嫁给Spring的API。如果开发者按照约定的或者用自动产生的工具(appgen不错)来编写数 据访问层,是没有线程安全性的问题的。Spring本身不提供这方面的保证。
    或者bean的定义为Singletons="false",也可以参考前面的一篇文章 Thread safety, singletons and Spring,用lookup-method。<pro spring> charpter 5介绍的更详细:
    Lookup Method Injection was added to Spring to overcome the problems encountered when a bean depends on another bean with a different lifecycle—specifically, when a singleton depends on a non-singleton. In this situation, both setter and constructor injection result in the singleton maintaining a single instance of what should be a non-singleton bean. In some cases, you will want to have the singleton bean obtain a new instance of the non-singleton every time it requires the bean in question.
    显然,如果A(Singletons) depends B(Propotype),使用这种方式可以避免A对B的访问并发和争用的问题。
     <pro spring>这本书也对Singletons=“true/false"的选择做了个小结:
    使用Singletons的情况有:
    1.Shared objects with no state;
    2.Shared object with read-only state;
    3.Shared object with shared state;
    4.High throughput objects with writable state. (synchronizing is need)
    使用propotype的情况有:
    1.Objects with writable stat;
    2.Objects with private state.

    与Spring的高度灵活不同,EJB的规范将同步作为一个服务(one of primary services),开发者开编写bean时不必考虑(也不能)线程相关的问题。session bean其分为两类,也有同步上的考虑。
    虽然thread-safe的问题总是存在,EJB也没有从本质上解决这个问题,但是其提出了这个问题,并给出了规范。

相关 [spring bean 线程安全] 推荐:

Spring单例Bean和线程安全 - duanxz - 博客园

- -
Spring的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢. 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢.

Spring bean生命周期

- - 开源软件 - ITeye博客
Spring bean生命周期.       在传统的Java应用中,Bean的生命周期非常简单.  Java的关键词new用来实例化Bean(或许他是非序列化的).  相反,Bean的生命周期在Spring容器中更加细致.  理解Spring Bean的生命周期非常重要,因为你或许要利用Spring提供的机会来订制Bean的创建过程.

spring中Bean的生命周期总结

- - CSDN博客推荐文章
spring中在ApplicationContext或在BeanFactory中Bean的生命周期总结.  Spring中Bean的生命周期,在学习spring的过程中bean的生命周期理解对学习spring有很大的帮助,下面我就分别介绍在ApplicationContext和BeanFactory中Bean的生命周期.

Spring两种加载创建spring bean配置文件的方式

- - ITeye博客
基于spring做bean的管理,基于web开发时有两种加载配置文件创建bean的方式. 第一种基于DispatcherServlet的init-param:. 第二种基于ContextLoaderListener:. PS:如果两种方式都采用了那么spring容器中会维护两套bean,如果有调度,定时任务等那么会重复执行.

Java中如何获取Spring中配置的bean

- - CSDN博客推荐文章
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架. 二、如何在程序中获取Spring配置的bean呢. 方法一:在初始化时保存ApplicationContext对象. 说明:这种方式适用于采用Spring框架的独立应用程序,需要程序通过配置文件手工初始化Spring的情况.

使用Java注解进行Spring bean管理

- - 编程语言 - ITeye博客
原文链接: http://www.ibm.com/developerworks/cn/webservices/ws-springjava/. 使用 Java 配置进行 Spring bean 管理. 学习使用 Java 配置管理 Spring bean. Spring bean 是使用传统的 XML 方法配置的.

spring bean 不使用注入的方式获取的两种方式

- - 开源软件 - ITeye博客
非注入方式取得spring注入bean的util类实现. 第一种,我用在webservice接口中. * 通过spring配置文件中配置的bean id取得bean对象. * @param id spring bean ID值. * @return spring bean对象. 实现ApplicationContextAware的Bean,在Bean被初始后,将会被注入ApplicationContext的实例.

Spring并发访问的线程安全性问题

- - 寒江孤影
和Struts一样,Spring的Controller默认是Singleton的,这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:一是我们不用每次创建Controller,二是减少了对象创建和垃圾收集的时间;由于只有一个Controller的instance,当多个线程调用它的时候,它里面的instance变量就不是线程安全的了,会发生窜数据的问题.

Spring单实例、多线程安全、事务解析

- - zzm
 在使用Spring时,很多人可能对Spring中为什么DAO和Service对象采用单实例方式很迷惑,这些读者是这么认为的:.     DAO对象必须包含一个数据库的连接Connection,而这个Connection不是线程安全的,所以每个DAO都要包含一个不同的Connection对象实例,这样一来DAO对象就不能是单实例的了.

java bean对象之间复制属性

- - Java - 编程语言 - ITeye博客
在现在的企业级Java应用程序中, Java Bean被广泛的应用. 一堆的相关的划分也应运而生, 如DTO, DAO, BO, POJO, VO等. 这里不去管这些概念的细节, 如果你感兴趣,可以google之, 比如 这篇文章. 这里要讨论的问题是如何在不同的Java Bean对象之间复制它们的属性.