springmvccontroller⾼并发问题
有状态和⽆状态的对象基本概念
有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是⾮线程安全的。⼀般是prototype scope。
⽆状态对象(Stateless Bean),就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。⼀般是singleton scope。
如Struts2中的Action,假如内部有实例变量User,当调⽤新增⽤户⽅法时,user是⽤来保存数据,那么此action是有状态对象。多个线程同时访问此action时会造成user变量的不⼀致。所以action的scope要设计成prototype,或者,User类放到threadLocal⾥来保持多个线程不会造成User变量的乱串(此种场景没必要放到threadLocal内)。
⽽Service内部⼀般只有dao实例变量如userDao, 因为userDao是⽆状态的对象(内部⽆实例变量且不能保存数据),所以service也是⽆状态的对象。
对于那些会以多线程运⾏的单例类
局部变量不会受多线程影响,
成员变量会受到多线程影响。
多个线程调⽤同⼀个对象的同⼀个⽅法:
如果⽅法⾥⽆成员变量,那么不受任何影响;
如果⽅法⾥有成员变量,只有读操作,不受影响;存在写操作,考虑多线程影响值;
例如Web应⽤中的Servlet,每个⽅法中对局部变量的操作都是在线程⾃⼰独⽴的内存区域内完成的,所以是线程安全的。
对于成员变量的操作,可以使⽤ThreadLocal来保证线程安全。
springMVC中,⼀般Controller、service、DAO层的scope均是singleton;
每个请求都是单独的线程,即使同时访问同⼀个Controller对象,因为并没有修改Controller对象,相当于针对Controller对象⽽⾔,只是读操作,没有写操作,不需要做同步处理。
Service层、Dao层⽤默认singleton就⾏,虽然Service类也有dao这样的属性,但dao这些类都是没有状态信息的,也就是相当于不变(immutable)类,所以不影响。
Struts2中的Action因为会有User、BizEntity这样的实例对象,是有状态信息的,在多线程环境下是不安全的,所以Struts2默认的实现是Prototype模式。在Spring中,Struts2的Action中scope 要配成prototype作⽤域。
Spring并发访问的线程安全性问题
由于Spring MVC默认是Singleton的,所以会产⽣⼀个潜在的安全隐患。根本核⼼是instance变量保持状态的问题。这意味着每个request过来,系统都会⽤原有的instance去处理,这样导致了两个结果:
⼀是我们不⽤每次创建Controller,
⼆是减少了对象创建和垃圾收集的时间;
由于只有⼀个Controller的instance,当多个线程同时调⽤它的时候,它⾥⾯的instance变量就不是线程安全的了,会发⽣窜数据的问题。当然⼤多数情况下,我们根本不需要考虑线程安全的问题,⽐如dao,service等,除⾮在bean中声明了实例变量。因此,我们在使⽤spring mvc 的contrller时,应避免在controller中定义实例变量。
有⼏种解决⽅法:
1、在控制器中不使⽤实例变量
2、将控制器的作⽤域从单例改为原型,即在spring配置⽂件Controller中声明 scope="prototype",每次都创建新的controller
3、在Controller中使⽤ThreadLocal变量
这⼏种做法有好有坏,第⼀种,需要开发⼈员拥有较⾼的编程⽔平与思想意识,在编码过程中⼒求避免出现这种BUG,⽽第⼆种则是容器⾃动的对每个请求产⽣⼀个实例,由JVM进⾏垃圾回收,因此做到了线程安全。
使⽤第⼀种⽅式的好处是实例对象只有⼀个,所有的请求都调⽤该实例对象,速度和性能上要优于第⼆种,不好的地⽅,就是需要程序员⾃⼰去控制实例变量的状态保持问题。第⼆种由于每次请求都创建⼀个实例,所以会消耗较多的内存空间。
所以在使⽤spring开发web 时要注意,默认Controller、Dao、Service都是单例的
mvc实例
ThreadLocal和线程同步
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同⼀时间只有⼀个线程访问变量。这时该变量是多个线程共享的,
使⽤同步机制要求程序慎密地分析什么时候对变量进⾏读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较⼤。
⽽ThreadLocal则从另⼀个⾓度来解决多线程的并发访问。ThreadLocal会为每⼀个线程提供⼀个独⽴的变量副本,从⽽隔离了多个线程对数据的访问冲突。因为每⼀个线程都拥有⾃⼰的变量副本,从⽽也就没有必要对该变量进⾏同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
缺点:threadlocal变量会⼀直存在于controller对象中,但是controller对象是多线程复⽤的,如果这⼀次请求完成之后忘记把threadlocal中的信息清空,则在处理下⼀个请求时则会出现信息串⽤。
概括起来说,对于多线程资源共享的问题,同步机制采⽤了“以时间换空间”的⽅式,⽽ThreadLocal采⽤了“以空间换时间”的⽅式。前者仅提供⼀份变量,让不同的线程排队访问,⽽后者为每⼀个线程都提供了⼀份变量,因此可以同时访问⽽互不影响。