zoukankan      html  css  js  c++  java
  • (转)Spring并发访问的线程安全性问题(高度总结)

    下面的记录对spring中并发的总结。理论分析参考Spring中Singleton模式的线程安全,建议先看

    spring中的并发访问题:

    我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。
    那么对于有状态的bean呢?Spring对一些(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的bean采用ThreadLocal进行处理,让它们也成为线程安全的状态,因此有状态的Bean就可以在多线程中共享了。

    如果用有状态的bean,也可以使用用prototype模式,每次在注入的时候就重新创建一个bean,在多线程中互不影响。


    有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
    无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象  .不能保存数据,是不变类,是线程安全的。

    无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。

    有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。

    Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web 容器负责的。一个Servlet类在Application中只有一个实例存在,也就是有多个线程在使用这个实例。这是单例模式的应用。如Service层、Dao层用默认singleton就行,虽然Service类也有dao这样的属性,但dao这些类都是没有状态信息的,也就是相当于不变(immutable)类,所以不影响。Struts2中的Action因为会有User、BizEntity这样的实例对象,是有状态信息的,在多线程环境下是不安全的,所以Struts2默认的实现是Prototype模式。在Spring中,Struts2的Action中,scope要配成prototype作用域。 (单例模式-单例注册表实现和threadLocal-可以处理有状态的bean之间的关系)


    还有我们的实体bean,从客户端传递到后台的controller-->service-->Dao,这一个流程中,他们这些对象都是单例的,那么这些单例的对象在处理我们的传递到后台的实体bean不会出问题吗?
    答:[实体bean不是单例的],并没有交给spring来管理,每次我们都手动的New出来的【如EMakeType et = new EMakeType();】,所以即使是那些处理我们提交数据的业务处理类是被多线程共享的,但是他们处理的数据并不是共享的,数据时每一个线程都有自己的一份,所以在数据这个方面是不会出现线程同步方面的问题的。

    (在这里补充下自己在项目开发中对于实体bean在多线程中的处理:1。对于实体bean一般通过方法参数的的形式传递(参数是局部变量),所以多线程之间不会有影响。2.有的地方对于有状态的bean直接使用prototype原型模式来进行解决。3.对于使用bean的地方可以通过new的方式来创建)

    但是那些的在Dao中的xxxDao,或controller中的xxxService,这些对象都是单例那么就会出现线程同步的问题。但是话又说回来了,这些对象虽然会被多个进程并发访问,可我们访问的是他们里面的方法,这些类里面通常不会含有成员变量,那个Dao里面的ibatisDao是框架里面封装好的,已经被测试,不会出现线程同步问题了。所以出问题的地方就是我们自己系统里面的业务对象,所以我们一定要注意这些业务对象里面千万不能要独立成员变量,否则会出错。


    spring对那些个有状态bean使用ThreadLocal维护变量[仅仅是变量,因为线程同步的问题就是成员变量的互斥访问出问题]时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。


    对spring并发访问线程安全的两篇博客汇总,可以得出上述结论..............

    由于Spring MVC默认是Singleton的,所以会产生一个潜在的安全隐患。根本核心是instance的变量保持状态的问题。这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果(单例的好处):

    一是我们不用每次创建Controller,
    二是减少了对象创建和垃圾收集的时间;
    由于只有一个Controller的instance,当多个线程同时调用它的时候,它里面的instance变量(可以理解为私有变量)就不是线程安全的了,会发生窜数据的问题。
    当然大多数情况下,我们根本不需要考虑线程安全的问题,比如dao,service等,除非在bean中声明了实例变量。因此,我们在使用spring mvc 的contrller时,应避免在controller中定义实例变量(singleton唯一的不好是单例的变量容易出现问题,下面有解决的方案)
    如:

    publicclassControllerextendsAbstractCommandController{
    ......
    protectedModelAndView handle(HttpServletRequest request,HttpServletResponse response,
    Object command,BindException errors)throwsException{
    company =................;
    }
    protectedCompany company;
    }

    在这里有声明一个变量company,这里就存在并发线程安全的问题。
    如果控制器是使用单例形式,且controller中有一个私有的变量a,所有请求到同一个controller时,使用的a变量是共用的,即若是某个请求中修改了这个变量a,则,在别的请求中能够读到这个修改的内容。。

    有几种解决方法:
    1、在控制器中不使用实例变量(可以使用方法参数的形式解决,参考博文 Spring Bean Scope 有状态的Bean 无状态的Bean
    2、将控制器的作用域从单例改为原型,即在spring配置文件Controller中声明 scope="prototype",每次都创建新的controller
    3、在Controller中使用ThreadLocal变量

    这几种做法有好有坏,第一种,需要开发人员拥有较高的编程水平与思想意识,在编码过程中力求避免出现这种BUG,

    而第二种则是容器自动的对每个请求产生一个实例,由JVM进行垃圾回收,因此做到了线程安全。

    使用第一种方式的好处是实例对象只有一个,所有的请求都调用该实例对象,速度和性能上要优于第二种,不好的地方,就是需要程序员自己去控制实例变量的状态保持问题。第二种由于每次请求都创建一个实例,所以会消耗较多的内存空间。

    所以在使用spring开发web 时要注意,默认Controller、Dao、Service都是单例的。


    【1】SpringMVC多线程环境中如何保证对象的安全性?

    代码如下:

    @RequestMapping("/user") @Controller ClassUserController { @Resource UserService userService; @RequestMapping("/add") publicvoid testA(User user){ userService.add(user); } @RequestMapping("/get") publicvoid testA(int id){ userService.get(id); } } @Service("userService") ClassUserService{ publicstaticMap<Integer,User> usersCache =newHashMap<String,User>(); publicvoid add(User user){ usersCache.put(user.getId(),user); } publicvoidget(int id){ usersCache.get(id); } }

    此段代码,usersCache对象就是线程不安全的。因为它是静态的全局共享对象。如果有多个线程同时调用add方法,可能会发生用户对象被覆盖的情况,也就是id对应对象不一致,这是多线程编程中最常发生的事情。

    所以,可以使用 Collections 工具同步Map。

    static Map<Integer, Users> usersCache = Collections.synchronizedMap(new HashMap<Integer, Users>());

    研究一下,Spring中的源码,它对常用的开源框架做了大量封装,如,Hibernate中的sessionFactory,就使用的是 org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean,而在 AnnotationSessionFactoryBean的父类LocalSessionFactoryBean中,定义了大量的ThreadLocal来保证多线程的安全性。 

    publicclassLocalSessionFactoryBeanextendsAbstractSessionFactoryBeanimplementsBeanClassLoaderAware{ privatestaticfinalThreadLocal<DataSource> configTimeDataSourceHolder = newThreadLocal<DataSource>(); privatestaticfinalThreadLocal<TransactionManager> configTimeTransactionManagerHolder = newThreadLocal<TransactionManager>(); privatestaticfinalThreadLocal<Object> configTimeRegionFactoryHolder = newThreadLocal<Object>(); privatestaticfinalThreadLocal<CacheProvider> configTimeCacheProviderHolder = newThreadLocal<CacheProvider>(); privatestaticfinalThreadLocal<LobHandler> configTimeLobHandlerHolder = newThreadLocal<LobHandler>();

    }

  • 相关阅读:
    Can't remove netstandard folder from output path (.net standard)
    website项目的reference问题
    The type exists in both DLLs
    git常用配置
    Map dependencies with code maps
    How to check HTML version of any website
    Bootstrap UI 编辑器
    网上职位要求对照
    Use of implicitly declared global variable
    ResolveUrl in external JavaScript file in asp.net project
  • 原文地址:https://www.cnblogs.com/lixuwu/p/5676121.html
Copyright © 2011-2022 走看看