Spring的单例与线程安全

Spring单例在高并发下可能出现的线程安全问题:

只有当注入的对象是无状态的,才能保证执行前后不被修改,否则执行一次之后单例对象就会发生变化,下一次执行结果肯定不一样。在高并发的情况下,这个线程刚使用单例对象进行属性设置,如果这时候被另外一个线程拿去使用,很有可能就造成这个对象就是一个脏对象,对这个脏对象的操作,都会存在线程安全的问题。

所以在高并发情况下,单利对象的数据不可以在一个线程使用过,另一个线程调用时单例对象的数据发生改变。 其实单例对象相当于全局变量,线程执行时需要修改数据,再高并发的情况下就会出现当前线程获取到的单例对象数据是脏数据。

Spring框架里的bean,不管是通过注解的方式,还是通过配置bean配置文件的方式,获取实例的时候都是默认的单例模式,除非在bean声明的时候指定为多例模式(scope=property),这是在多线程开发的时候要尤其注意的地方。单例模式,顾名思义就是全局只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

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

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

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

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

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

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

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

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

举一个最明显的例子,一般情况下,我们会把请求和响应、处理都封装成三个类,如下:

request请求体:

public class Request {
	// 请求参数1
	int reqParams1;
	
	// 请求参数2
	String reqParams2;

	public int getReqParams1() {
		return reqParams1;
	}

	public void setReqParams1(int reqParams1) {
		this.reqParams1 = reqParams1;
	}

	public String getReqParams2() {
		return reqParams2;
	}

	public void setReqParams2(String reqParams2) {
		this.reqParams2 = reqParams2;
	}
}

response响应体:

public class Response {
	// 响应参数1
	int  rspParams1;
	
	// 响应参数2
	String rspParams2;

	public int getRspParams1() {
		return rspParams1;
	}

	public void setRspParams1(int rspParams1) {
		this.rspParams1 = rspParams1;
	}

	public String getRspParams2() {
		return rspParams2;
	}

	public void setRspParams2(String rspParams2) {
		this.rspParams2 = rspParams2;
	}
}

业务处理serviceProcess

public class ServiceProcess {
	public Response serviceProcess(Request req)
	{
		// 处理请求req的具体逻辑......

		// 然后构造response返回
		return new Response();
	}
}

一般我们的请求体和响应体都是不会声明成bean对象的,而是在业务处理部分new一个出来,这种情况下这两个类是不存在单例以及线程安全问题。但是业务处理部分一般都会声明成bean对象,然后供框架代码调用,这个bean对象就是单例,这个类对象不能声明普通类成员变量,否则很容易引入线程安全问题,因为普通类成员变量的声明周期是随着这个类对象的实例终结才终结,在单例对象中,随时都有可能被修改,一旦被修改,而下一个线程使用的时候,就会出现意想不到的线程安全问题。

所以,以下这个代码块中的类成员变量i,是有线程安全问题的,为了避免单例对象出现成员变量被修改的情况,我们都是将成员变量声明不可修改的常量,或者局部变量(k),如下面这个代码块的成员变量j,只能够读取,不能修改

public class ServiceProcess {
	public int i = 1;
	
	public static final int j = 1;
	
	public Response serviceProcess(Request req)
	{
        int k = 1;
		// 处理请求req的具体逻辑过程中,修改了类成员变量i......
		
		// 然后构造response返回
		return new Response();
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值