Spring 单例 多例 线程安全等问题,想请教大家

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 单例 多例 线程安全等问题,想请教大家相关的知识,希望对你有一定的参考价值。

本人最近研究Spring,得到以下结论,不知对不对,想请大家帮忙看看
1、在多线程环境中,线程安全不安全都在于类里面有没有成员变量,这些成员变量都是不安全的。
2、pojo应该都设成prototype
3、bean里面如果有成员变量,如String,int类型的成员变量,那这些成员变量肯定是不安全的,如果是引用其他bean,则要看引用的bean里面有没有成员变量,如果有,则通常也应该是不安全的(除非这个引用的bean内容做了安全处理)。
4、一个单例bean A引用了一个多例的bean B为自己的成员变量,由于 beean A是单例,容器只加载一次,所以他所引用的beanB就不安全了。目前测试出来的唯一办法是使用ThreadLocal<>来封装beanB里面的成员变量,如下:
@Service("serviceTest2")
public class ServiceTest2Impl implements ServiceTest2
private ThreadLocal<GjjInfo> threadLocal=new ThreadLocal<GjjInfo>();
private GjjInfo getGjjInfo()
if(threadLocal.get()==null)
System.out.println("gjjInfo为空,取一下");
threadLocal.set(SpringContextHolder.getBean(GjjInfo.class));

return threadLocal.get();

public void start(Socket socket)
GjjInfo gjjInfo=getGjjInfo();
System.out.println(Thread.currentThread().getName()+"|gjjInfo:"+gjjInfo.getAreaCode());
gjjInfo.setAreaCode("bbbbbbbbb");


这样设计可以保证多线程环境中调用serviceTest2是线程安全的,但是在自己的当前线程中也不一定安全,例如,有一个serviceTest1一下子@Autowired了两个serviceTest2(oneServiceTest2,twoServiceTest2),那么这两个serviceTest2一模一样(即便是用ThreadLocal<>封装了),假如:
情况1: oneServiceTest2处理业务逻辑到一半,转头又处理twoServiceTest2,肯定就有了脏读的可能。
情况2: oneServiceTest2处理结束,但是成员变量有变化,这时候再处理twoServiceTest2,则twoServiceTest2必然脏读。
所以我得到的结论是:在一次请求的整个生命周期,当前线程中某个带有成员变量的bean,如果只被调用一次,那么用ThreadLocal<>来封装成员变量,是没有问题的,但是如果他被多次调用,那还是设成prototype比较好。

Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring并没有确保这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码。
Spring对每个bean提供了一个scope属性来表示该bean的作用域。它是bean的生命周期。

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。
ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal是一个为线程提供线程局部变量的工具类。它的思想也十分简单,就是为线程提供一个线程私有的变量副本,这样多个线程都可以随意更改自己线程局部的变量,不会影响到其他线程。不过需要注意的是,ThreadLocal提供的只是一个浅拷贝,如果变量是一个引用类型,那么就要考虑它内部的状态是否会被改变,想要解决这个问题可以通过重写ThreadLocal的initialValue()函数来自己实现深拷贝,建议在使用ThreadLocal时一开始就重写该函数。
ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
参考技术A

你的问题都看了,其实就是线程安全问题,跟spring并没有什么关系。需要清楚两点

    线程安全问题是多线程访问共享数据才会存在的,这包括两种情况,一是多线程访问单例的成员变量,二是多线程访问静态变量(数据库这里这就不算了)

    线程内部不会产生安全问题,从java内存模型来看,一个线程对于对象的操作流程应该是,访问主存中的对象并复制到工作内存当中,在工作内存中对副本做相应操作,操作完成后再写入主存,线程见会产生线程安全问题,正是因为一个线程在操作工作内存中的副本时,在写入主存之前,其他线程是不可见的,所以会产生问题,而线程内部,无论哪个方法怎么执行,对副本都是可见的,你说的两种情况其实也是不存在的,情况1,如果你在一个线程中,一个方法执行到一半,去执行另一个方法,代码怎么写?肯定是在第一个方法中调用第二个方法,那么第二个方法执行完,第一个方法的后半部分肯定要接着第二个的逻辑写,还接着第一个方法的前一半写,那就是代码写错了,情况2相同

这两点应该可以解答你所有的问题

以上是关于Spring 单例 多例 线程安全等问题,想请教大家的主要内容,如果未能解决你的问题,请参考以下文章

spring使用单例 线程怎么解决并发

spring的controller是单例还是多例,怎么保证并发的安全

Spring中的Bean默认是单例还是多例?如何保证并发安全?

单例模式和多例模式

spring中创建bean对象时多例和单例的区别

spring单例和非单例的问题