实例化bean的三种方式Spring的并发问题——有状态Bean和⽆状态Bean
⼀、有状态和⽆状态
有状态会话bean  :每个⽤户有⾃⼰特有的⼀个实例,在⽤户的⽣存期内,bean保持了⽤户的信息,即“有状态”;⼀旦⽤户灭亡(调⽤结束或实例结束),bean的⽣命期也告结束。即每个⽤户最初都会得到⼀个初始的bean。简单来说,有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是⾮线程安全的。
⽆状态会话bean  :bean⼀旦实例化就被加进会话池中,各个⽤户都可以共⽤。即使⽤户已经消亡,bean  的⽣命期也不⼀定结束,它可能依然存在于会话池中,供其他⽤户调⽤。由于没有特定的⽤户,那么也就不能保持某⼀⽤户的状态,所以叫⽆状态bean。但⽆状态会话bean  并⾮没有状态,如果它有⾃⼰的属性(变量),那么这些变量就会受到所有调⽤它的⽤户的影响,这是在实际应⽤中必须注意的。简单来说,⽆状态就是⼀次操作,不能保存数据。⽆状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。
package com.sw;
public class TestManagerImpl implements TestManager{
private User user;    //有⼀个记录信息的实例
public void deleteUser(User e) throws Exception {
user = e ;          //1
prepareData(e);
}
public void prepareData(User e) throws Exception {
user = Id());            //2
.....
//使⽤Id();                      //3
.....
.....
}
}
⼀个有状态的bean
⼆、解决有状态bean的线程安全问题
Spring对bean的配置中有四种配置⽅式,我们只说其中两种:singleton单例模式、prototype原型模式。
<bean id="testManager" class="com.sw.TestManagerImpl" scope="singleton" />
<bean id="testManager" class="com.sw.TestManagerImpl" scope="prototype" />
默认的配置是singleton。
singleton表⽰该bean全局只有⼀个实例。
prototype表⽰该bean在每次被注⼊的时候,都要重新创建⼀个实例,这种情况适⽤于有状态的Bean。
如果对有状态的bean使⽤了singleton的话会出现线程安全问题。
例如上⾯的例⼦
如果有两个⽤户同时访问
假定为user1,user2
当user1 调⽤到程序中的1步骤的时候,该Bean的私有变量user被付值为user1
当user1的程序⾛到2步骤的时候,该Bean的私有变量user被重新付值为user1_create
理想的状况,当user1⾛到3步骤的时候,私有变量user应该为user1_create;
但如果在user1调⽤到3步骤之前,user2开始运⾏到了1步骤了,由于单态的资源共享,则私有变量user被修改为user2
这种情况下,user1的步骤3⽤到的Id()实际⽤到是user2的对象。
对于这种情况我们可以这样解决
1.将有状态的bean配置成prototype模式,让每⼀个线程都创建⼀个prototype实例。但是这样会产⽣很多的实例消耗较多的内存空间。
2.使⽤ThreadLocal变量,为每⼀条线程设置变量副本。
使⽤ThreadLocal的例⼦:
例如,我们有⼀个银⾏的BankDAO类和⼀个个⼈账户的PeopleDAO类,现在需要个⼈向银⾏进⾏转账,在PeopleDAO类中有⼀个账户减少的⽅法,BankDAO类中有⼀个账户增加的⽅法,那么这两个⽅法在调⽤的时候必须使⽤同⼀个Connection数据库连接对象,如果他们使⽤两个Connection对象,则会开启两段事务,可能出现个⼈账户减少⽽银⾏账户未增加的现象。使⽤同⼀个Connection对象的话,在应⽤程序中可能会设置为⼀个全局的数据库连接对象,从⽽避免在调⽤每个⽅法时都传递⼀个Connection对象。问题是当我们把Connection对象设置为全局变量时,你不能保证是否有其他线程会将这个Connection对象关闭,这样就会出现线程安全问题。解决办法就是在进⾏转账操作这个线程中,使⽤ThreadLocal中获取Connection对象,这样,在调⽤个⼈账户减少和银⾏账户增加的线程中,就能从ThreadLocal中取到同⼀个Connection对象,并且这个Connection对象为转账操作这个线程独有,不会被其他线程影响,保证了线程安全性。
public class ConnectionHolder {
public static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
};
public static Connection getConnection(){
Connection connection = ();
if(null == connection){
connection = Connection(DB_URL);            connectionHolder.set(connection);
}
return connection;
}
}