spring单例、线程安全、事务等疑惑 收集

LittleMatches 2013-08-06

http://www.cnblogs.com/davidwang456/p/3832949.html

spring中管理的bean实例默认情况下是单例的[sigleton类型],就还有prototype类型按其作用域来讲有sigleton,prototype,request,session,globalsession。spring中的单例与设计模式里面的单例略有不同,设计模式的单例是在整个应用中只有一个实例,而spring中的单例是在一个IoC容器中就只有一个实例。但spring中的单例也不会影响应用的并发访问,【不会出现各个线程之间的等待问题,或是死锁问题】因为大多数时候客户端都在访问我们应用中的业务对象,而这些业务对象并没有做线程的并发限制,只是在这个时候我们不应该在业务对象中设置那些容易造成出错的成员变量,在并发访问时候这些成员变量将会是并发线程中的共享对象,那么这个时候就会出现意外情况。那么我们的Eic-server的所有的业务对象中的成员变量如,在Dao中的xxxDao,或controller中的xxxService,都会被多个线程共享,那么这些对象不会出现同步问题吗,比如会造成数据库的插入,更新异常?还有我们的实体bean,从客户端传递到后台的controller-->service-->Dao,这一个流程中,他们这些对象都是单例的,那么这些单例的对象在处理我们的传递到后台的实体bean不会出问题吗?

答:[实体bean不是单例的],并没有交给spring来管理,每次我们都手动的New出来的【如EMakeTypeet=newEMakeType();】,所以即使是那些处理我们提交数据的业务处理类是被多线程共享的,但是他们处理的数据并不是共享的,数据时每一个线程都有自己的一份,所以在数据这个方面是不会出现线程同步方面的问题的。但是那些的在Dao中的xxxDao,或controller中的xxxService,这些对象都是单例那么就会出现线程同步的问题。但是话又说回来了,这些对象虽然会被多个进程并发访问,可我们访问的是他们里面的方法,这些类里面通常不会含有成员变量,那个Dao里面的ibatisDao是框架里面封装好的,已经被测试,不会出现线程同步问题了。所以出问题的地方就是我们自己系统里面的业务对象,所以我们一定要注意这些业务对象里面千万不能要独立成员变量,否则会出错。

Spring中由容器托管的类如果没有特殊声明(scope=“prototype”),则默认为单列模式,当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对该单列状态的修改(成员变量),则必须考虑线程同步问题;否则由于在业务逻辑中执行所需的局部变量会分配在栈空间中,所以不需要同步。

其实函数本身是代码,代码是只读的,无论多少个线程同时调都无所谓(因为只是读的嘛),但是函数中肯定是要用到数据的,如果数据是函数参数、局部变量,那么这些数据都是存在每个线程自己的栈上的,同时调用是没有关系的,不会涉及到线程安全资源共享的问题。但是如果使用到了全局静态变量或者类的成员变量的时候。就会出现数据安全的问题,还有,如果我们的成员变量在函数体内如果只进行读操作,不进行写操作,也是线程安全的。

简而言之一句话,单例的方法在同一个时刻是可以被两个线程同时调用的。我们在做程序的时候要尽可能少的使用类的成员变量,如果使用成员变量,尽量保证只对成员变量进行读操作

当我们的很多用户去修改自己的信息的时候,用户线程会通过调用dao(dao我们都是给注入有配链接池的数据源的),dao会拿到链接池中的一个链接,将我们要处理的信息(sql语句等)交付个数据库,数据库会按照自己的多线程处理机制完成线程的同步,然后进行数据安全处理,线程在完成数据处理后会将占有的链接放回到链接池中。

——————————————————————————————————————

以下来自http://www.cnblogs.com/doit8791/p/4093808.html

Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方。

单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对该单列状态的修改(体现为该单列的成员属性),则必须考虑线程同步问题

同步机制的比较  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用

 概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

Spring使用ThreadLocal解决线程安全问题

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

一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。 线程安全问题都是由全局变量及静态变量引起的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

1)常量始终是线程安全的,因为只存在读操作。

2)每次调用方法前都新建一个实例是线程安全的,因为不会访问共享的资源。

3)局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量和方法内变量。

有状态就是有数据存储功能。有状态对象(StatefulBean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。

无状态就是一次操作,不能保存数据。无状态对象(StatelessBean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。

有状态对象:

无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype:每次对bean的请求都会创建一个新的bean实例。

Struts2默认的实现是Prototype模式。也就是每个请求都新生成一个Action实例,所以不存在线程安全问题。需要注意的是,如果由Spring管理action的生命周期,scope要配成prototype作用域。

二、线程安全案例:

SimpleDateFormat(下面简称sdf)类内部有一个Calendar对象引用,它用来储存和这个sdf相关的日期信息,例如sdf.parse(dateStr),sdf.format(date)诸如此类的方法参数传入的日期相关String,Date等等,都是交友Calendar引用来储存的.这样就会导致一个问题,如果你的sdf是个static的,那么多个thread之间就会共享这个sdf,同时也是共享这个Calendar引用,并且,观察sdf.parse()方法,你会发现有如下的调用:

Dateparse(){

calendar.clear();//清理calendar

...//执行一些操作,设置calendar的日期什么的

calendar.getTime();//获取calendar的时间

}

这里会导致的问题就是,如果线程A调用了sdf.parse(),并且进行了calendar.clear()后还未执行calendar.getTime()的时候,线程B又调用了sdf.parse(),这时候线程B也执行了sdf.clear()方法,这样就导致线程A的的calendar数据被清空了(实际上A,B的同时被清空了).又或者当A执行了calendar.clear()后被挂起,这时候B开始调用sdf.parse()并顺利i结束,这样A的calendar内存储的的date变成了后来B设置的calendar的date

这个问题背后隐藏着一个更为重要的问题--无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。

这也同时提醒我们在开发和设计系统的时候注意下一下三点:

1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明

2.对线程环境下,对每一个共享的可变变量都要注意其线程安全性

3.我们的类和方法在做设计的时候,要尽量设计成无状态的

 三.解决办法

1.需要的时候创建新实例:

说明:在需要用到SimpleDateFormat的地方新建一个实例,不管什么时候,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加重了创建对象的负担。在一般情况下,这样其实对性能影响比不是很明显的。

2.使用同步:同步SimpleDateFormat对象

publicclassDateSyncUtil{

privatestaticSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");

publicstaticStringformatDate(Datedate)throwsParseException{

synchronized(sdf){

returnsdf.format(date);

}

}

publicstaticDateparse(StringstrDate)throwsParseException{

synchronized(sdf){

returnsdf.parse(strDate);

}

}

}

说明:当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。

3.使用ThreadLocal: 

publicclassConcurrentDateUtil{

privatestaticThreadLocal<DateFormat>threadLocal=newThreadLocal<DateFormat>(){

@Override

protectedDateFormatinitialValue(){

returnnewSimpleDateFormat("yyyy-MM-ddHH:mm:ss");

}

};

publicstaticDateparse(StringdateStr)throwsParseException{

returnthreadLocal.get().parse(dateStr);

}

publicstaticStringformat(Datedate){

returnthreadLocal.get().format(date);

}

}

publicclassThreadLocalDateUtil{

privatestaticfinalStringdate_format="yyyy-MM-ddHH:mm:ss";

privatestaticThreadLocal<DateFormat>threadLocal=newThreadLocal<DateFormat>();

publicstaticDateFormatgetDateFormat()

{

DateFormatdf=threadLocal.get();

if(df==null){

df=newSimpleDateFormat(date_format);

threadLocal.set(df);

}

returndf;

}

publicstaticStringformatDate(Datedate)throwsParseException{

returngetDateFormat().format(date);

}

publicstaticDateparse(StringstrDate)throwsParseException{

returngetDateFormat().parse(strDate);

}

}

说明:使用ThreadLocal,也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。

4.抛弃JDK,使用其他类库中的时间格式化类:

1.使用Apachecommons里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat,可惜它只能对日期进行format,不能对日期串进行解析。

2.使用Joda-Time类库来处理时间相关问题

做一个简单的压力测试,方法一最慢,方法三最快,但是就算是最慢的方法一性能也不差,一般系统方法一和方法二就可以满足,所以说在这个点很难成为你系统的瓶颈所在。从简单的角度来说,建议使用方法一或者方法二,如果在必要的时候,追求那么一点性能提升的话,可以考虑用方法三,用ThreadLocal做缓存。

Joda-Time类库对时间处理方式比较完美,建议使用。

----------------------------------------------------------------------

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

在使用Spring时,很多人可能对Spring中为什么DAO和Service对象采用单实例方式很迷惑,这些读者是这么认为的:

DAO对象必须包含一个数据库的连接Connection,而这个Connection不是线程安全的,所以每个DAO都要包含一个不同的Connection对象实例,这样一来DAO对象就不能是单实例的了。

上述观点对了一半。对的是“每个DAO都要包含一个不同的Connection对象实例”这句话,错的是“DAO对象就不能是单实例”。

其实Spring在实现Service和DAO对象时,使用了ThreadLocal这个类,这个是一切的核心!如果你不知道什么事ThreadLocal,请看《深入研究java.lang.ThreadLocal类》:。请放心,这个类很简单的。

要弄明白这一切,又得明白事务管理在Spring中是怎么工作的,所以本文就对Spring中多线程、事务的问题进行解析。

Spring使用ThreadLocal解决线程安全问题:

Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中,该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。

参考下面代码,这个是《Spring3.x企业应用开发实战中的例子》,本文后面也会多次用到该书中例子(有修改)。

publicclassSqlConnection{

//①使用ThreadLocal保存Connection变量

privatestaticThreadLocal<Connection>connThreadLocal=newThreadLocal<Connection>();

publicstaticConnectiongetConnection(){

//②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,

//并将其保存到线程本地变量中。

if(connThreadLocal.get()==null){

Connectionconn=getConnection();

connThreadLocal.set(conn);

returnconn;

}else{

returnconnThreadLocal.get();

//③直接返回线程本地变量

}

}

publicvoidaddTopic(){

//④从ThreadLocal中获取线程对应的Connection

try{

Statementstat=getConnection().createStatement();

}catch(SQLExceptione){

e.printStackTrace();

}

}

}这个是例子展示了不同线程使用TopicDao时如何使得每个线程都获得不同的Connection实例副本,同时保持TopicDao本身是单实例。

事务管理器:

事务管理器用于管理各个事务方法,它产生一个事务管理上下文。下文以SpringJDBC的事务管理器DataSourceTransactionManager类为例子。

我们知道数据库连接Connection在不同线程中是不能共享的,事务管理器为不同的事务线程利用ThreadLocal类提供独立的Connection副本。事实上,它将Service和Dao中所有线程不安全的变量都提取出来单独放在一个地方,并用ThreadLocal替换。而多线程可以共享的部分则以单实例方式存在。

事务传播行为:

当我们调用Service的某个事务方法时,如果该方法内部又调用其它Service的事务方法,则会出现事务的嵌套。Spring定义了一套事务传播行为,请参考。这里我们假定都用的REQUIRED这个类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到的当前事务。参考下面例子(代码不完整):

@Service("userService")

publicclassUserServiceextendsBaseService{

@Autowired

privateJdbcTemplatejdbcTemplate;

@Autowired

privateScoreServicescoreService;

publicvoidlogon(StringuserName){

updateLastLogonTime(userName);

scoreService.addScore(userName,20);

}

publicvoidupdateLastLogonTime(StringuserName){

Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";

jdbcTemplate.update(sql,System.currentTimeMillis(),userName);

}

publicstaticvoidmain(String[]args){

ApplicationContextctx=newClassPathXmlApplicationContext("com/baobaotao/nestcall/applicatonContext.xml");

UserServiceservice=(UserService)ctx.getBean("userService");

service.logon("tom");

}

}

@Service("scoreUserService")

publicclassScoreServiceextendsBaseService{

@Autowired

privateJdbcTemplatejdbcTemplate;

publicvoidaddScore(StringuserName,inttoAdd){

Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";

jdbcTemplate.update(sql,toAdd,userName);

}

}同时,在配置文件中指定UserService、ScoreService中的所有方法都开启事务。

上述例子中UserService.logon()执行开始时Spring创建一个新事务,UserService.updateLastLogonTime()和ScoreService.addScore()会加入这个事务中,好像所有的代码都“直接合并”了!

多线程中事务传播的困惑:

还是上面那个例子,加入现在我在UserService.logon()方法中手动新开一个线程,然后在新开的线程中执行ScoreService.add()方法,此时事务传播行为会怎么样?飞线程安全的变量,比如Connection会怎样?改动之后的UserService代码大体是:

@Service("userService")

publicclassUserServiceextendsBaseService{

@Autowired

privateJdbcTemplatejdbcTemplate;

@Autowired

privateScoreServicescoreService;

publicvoidlogon(StringuserName){

updateLastLogonTime(userName);

ThreadmyThread=newMyThread(this.scoreService,userName,20);//使用一个新线程运行

myThread.start();

}

publicvoidupdateLastLogonTime(StringuserName){

Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";

jdbcTemplate.update(sql,System.currentTimeMillis(),userName);

}

privateclassMyThreadextendsThread{

privateScoreServicescoreService;

privateStringuserName;

privateinttoAdd;

privateMyThread(ScoreServicescoreService,StringuserName,inttoAdd){

this.scoreService=scoreService;

this.userName=userName;

this.toAdd=toAdd;

}

publicvoidrun(){

scoreService.addScore(userName,toAdd);

}

}

publicstaticvoidmain(String[]args){

ApplicationContextctx=newClassPathXmlApplicationContext("com/baobaotao/multithread/applicatonContext.xml");

UserServiceservice=(UserService)ctx.getBean("userService");

service.logon("tom");

}

}这个例子中,MyThread会新开一个事务,于是UserService.logon()和UserService.updateLastLogonTime()会在一个事务中,而ScoreService.addScore()在另一个事务中,需要注意的是这两个事务都被事务管理器放在事务上下文中。

结论是:在事务属性为REQUIRED时,在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果互相嵌套调用的事务方法工作在不同线程中,则不同线程下的事务方法工作在独立的事务中。

底层数据库连接Connection访问问题

程序只要使用SpringDAO模板,例如JdbcTemplate进行数据访问,一定没有数据库连接泄露问题!如果程序中显式的获取了数据连接Connection,则需要手工关闭它,否则就会泄露!

当Spring事务方法运行时,事务会放在事务上下文中,这个事务上下文在本事务执行线程中对同一个数据源绑定了唯一一个数据连接,所有被该事务的上下文传播的放发都共享这个数据连接。这一切都在Spring控制下,不会产生泄露。Spring提供了数据资源获取工具类DataSourceUtils来获取这个数据连接.

@Service("jdbcUserService")

publicclassJdbcUserService{

@Autowired

privateJdbcTemplatejdbcTemplate;

@Transactional

publicvoidlogon(StringuserName){

try{

Connectionconn=jdbcTemplate.getDataSource().getConnection();

Stringsql="UPDATEt_userSETlast_logon_time=?WHEREuser_name=?";

jdbcTemplate.update(sql,System.currentTimeMillis(),userName);

}catch(Exceptione){

e.printStackTrace();

}

}

publicstaticvoidasynchrLogon(JdbcUserServiceuserService,StringuserName){

UserServiceRunnerrunner=newUserServiceRunner(userService,userName);

runner.start();

}

publicstaticvoidreportConn(BasicDataSourcebasicDataSource){

System.out.println("连接数[active:idle]-["+

basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");

}

privatestaticclassUserServiceRunnerextendsThread{

privateJdbcUserServiceuserService;

privateStringuserName;

publicUserServiceRunner(JdbcUserServiceuserService,StringuserName){

this.userService=userService;

this.userName=userName;

}

publicvoidrun(){

userService.logon(userName);

}

}

publicstaticvoidmain(String[]args){

ApplicationContextctx=newClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml");

JdbcUserServiceuserService=(JdbcUserService)ctx.getBean("jdbcUserService");

JdbcUserService.asynchrLogon(userService,"tom");

}

}在这个例子中,main线程拿到一个UserService实例,获取一个Connection的副本,它会被Spring管理,不会泄露。UserServiceRunner线程手动从数据源拿了一个Connection但没有关闭因此会泄露。

如果希望使UserServiceRunner能拿到UserService中那个Connection们就要使用DataSourceUtils类,DataSourceUtils.getConnection()方法会首先查看当前是否存在事务管理上下文,如果存在就尝试从事务管理上下文拿连接,如果获取失败,直接从数据源中拿。在获取连接后,如果存在事务管理上下文则把连接绑定上去。

实际上,上面的代码只用改动一行,把login()方法中获取连接那行改成就可以做到:

Connectionconn=DataSourceUtils.getConnection(jdbcTemplate.getDataSource());

需要注意的是:如果DataSourceUtils在没有事务上下文的方法中使用getConnection()获取连接,依然要手动管理这个连接!

此外,开启了事务的方法要在整个事务方法结束后才释放事务上下文绑定的Connection连接,而没有开启事务的方法在调用完Spring的Dao模板方法后立刻释放。

多线程一定要与事务挂钩么?

不是!即便没有开启事务,利用ThreadLocal机制也能保证线程安全,Dao照样可以操作数据。但是事务和多线程确实纠缠不清,上文已经分析了在多线程下事务传播行为、事务对Connection获取的影响。

结论:

•Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中,该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。

•在事务属性为REQUIRED时,在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果互相嵌套调用的事务方法工作在不同线程中,则不同线程下的事务方法工作在独立的事务中。

•程序只要使用SpringDAO模板,例如JdbcTemplate进行数据访问,一定没有数据库连接泄露问题!如果程序中显式的获取了数据连接Connection,则需要手工关闭它,否则就会泄露!

•当Spring事务方法运行时,就产生一个事务上下文,它在本事务执行线程中对同一个数据源绑定了一个唯一的数据连接,所有被该事务上下文传播的方法都共享这个连接。要获取这个连接,如要使用Spirng的资源获取工具类DataSourceUtils。

•事务管理上下文就好比一个盒子,所有的事务都放在里面。如果在某个事务方法中开启一个新线程,新线程中执行另一个事务方法,则由上面第二条可知这两个方法运行于两个独立的事务中,但是:如果使用DataSourcesUtils,则新线程中的方法可以从事务上下文中获取原线程中的数据连接!

相关推荐