`

Spring单例Bean和线程安全

 
阅读更多

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 单例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 raceconditions, deadlocks,livelocksand starvation.

Thread-local storage 

Variables arelocalized so that each thread has its own private copy. These variables retaintheir values acrosssubroutine 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 changesatomicallyto 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单例引起的线程安全问题

    在 Spring 框架中,bean 的实例化策略分为两种:单例(Singleton)和多例(Prototype)。单例模式意味着在整个 Spring 容器中,对于一个给定的 bean 定义,只会存在一个实例。这通常通过 @Scope("singleton") 注解来...

    Java 单例模式线程安全问题

    然而,在多线程环境下,单例模式可能会遇到线程安全问题,因为多个线程可能会同时访问同一个实例,从而导致数据不一致和其他问题。 在 Java 中,单例模式的实现可以使用双重检查锁机制、静态内部类和枚举类型等方式...

    Spring bean为什么默认是单例

    然而,单例Bean并非没有缺点。它的一个主要劣势是线程安全性。如果一个Bean是有状态的,即它包含可变的实例变量,那么在多线程环境中,多个线程可能会同时访问并修改这些变量,导致数据不一致。为了保证线程安全,...

    Java-spring框架面试题

    在本篇文章中,我们将详细讨论Java-Spring框架中的一些重要知识点,包括单例bean的线程安全性、AOP的应用、事务的实现、事务失效的场景、bean的生命周期、循环依赖等。 单例bean的线程安全性 在Spring框架中,单例...

    最新面试文档.pdf

    3. 使用ThreadLocal:在Bean内部声明ThreadLocal变量来存储可变数据,每个线程有自己的ThreadLocal副本,从而确保线程隔离和安全。 总的来说,Spring中的Bean是否线程安全取决于其作用域和状态。了解这些概念以及...

    基于Spring+Ibatis的安全线程实现

    2. **单例Bean的线程安全**:Spring默认的Bean作用域为单例,如果Bean不是无状态的,即其内部包含共享状态,那么需要谨慎处理线程安全问题。通常,我们可以通过将有状态Bean改为原型作用域,或者使用AOP在方法级别...

    25个经典的Spring面试问答

    【Spring单例Bean与线程安全】 Spring中的单例Bean默认不是线程安全的。如果多个线程同时访问一个非线程安全的单例Bean,可能会导致数据不一致。因此,应避免在单例Bean中存储状态,或者采取同步措施。 【注入Java ...

    25个经典的Spring面试问答.docx

    Spring框架的单例Bean是否线程安全取决于开发者的实现方式,如果使用了线程安全的方式,那么单例Bean就是线程安全的。Spring框架还提供了自动装配的功能,可以自动将Bean装配到应用程序中。 在Spring框架中,事件...

    java中spring里实现多线程

    此外,Spring的`@Scope`注解可以帮助管理单例bean在多线程环境下的状态,避免数据不一致。 总之,Spring通过提供高级的线程管理和调度工具,简化了在Java应用程序中实现多线程的复杂性。理解并熟练使用这些工具,能...

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

    在Spring MVC中,Controller被设计为单例模式,这是为了提高性能和减少内存消耗,因为每个请求都会重用相同的Controller实例。然而,这种设计也带来了线程安全问题,特别是当Controller内部包含可变的实例变量时。...

    Spring项目bean基础配置代码

    - `prototype`:每次请求都会创建一个新的Bean实例,适合那些需要多个实例的场景,比如实现线程安全的策略对象。 - `request`和`session`:这两个作用范围仅在Web应用中适用,分别对应于每个HTTP请求和session创建一...

    详解Spring中bean的scope以后使用

    当一个Bean被定义为Singleton时,Spring容器只会为这个Bean创建一个实例,并将其保存在单例缓存中。这意味着每当需要这个Bean时,Spring都会返回同一个实例。这有助于减少内存消耗和提高性能,尤其是在处理复杂的...

    2023最新Spring全家桶面试题-图灵徐庶

    17. Spring框架中的单例bean是线程安全的吗?:是的,但需要注意线程安全问题。 18. Spring如何处理线程并发问题?:通过使用synchronized关键字和Lock机制来处理线程并发问题。 19. 什么是bean装配?:是指将一个...

    我做了个简单的性能测试,发现从bean工厂里单例执行方法效率比new对象执行慢很多

    4. **缓存机制**:Spring的单例Bean在初始化后会被缓存,这看似应该提高性能,但如果Bean很大,缓存本身也会占用一定的内存和时间。 针对以上问题,可以采取以下优化策略: 1. **延迟初始化**:可以使用`...

    4 后台使用Spring中的Bean质量评估193210111党涛1

    在Web应用中,如果Bean被设计为单例,并且需要处理多个用户的请求,那么必须确保这些Bean是线程安全的。Spring提供了一些策略来确保Bean的安全性,如使用线程局部变量、同步方法或避免使用共享状态。 其次,最常见...

    Spring容器中Bean的作用域编程开发技术共3页.pd

    单例Bean在容器启动时被创建,并在整个容器生命周期中保持唯一。 2. **原型(Prototype)作用域**:与单例相反,原型作用域的Bean每次请求都会创建一个新的实例。这适用于需要多个实例的情况,如用户会话中的对象,...

Global site tag (gtag.js) - Google Analytics