写点什么

Spring 中的单例模式使用

作者:JavaEdge
  • 2022 年 1 月 19 日
  • 本文字数:3003 字

    阅读完需:约 10 分钟

Spring中的单例模式使用

1 spring 单例 V.S 设计模式的单例

  • 设计模式单例,在整个应用中只有一个实例

  • spring 单例,在一个 IoC 容器中只有一个实例


spring 中的单例也不影响应用并发访问。大多数时候客户端都在访问我们应用中的业务对象,为减少并发控制,不应该在业务对象中设置那些容易造成出错的成员变量

成员变量的解决方式

  1. 方法的参数,局部变量(相当于 new)

  2. threadlocal、设置 bean scope=prototype


Spring Bean Scope 有状态的Bean 无状态的Bean遇到的情况

Spring 并发问题

一般无状态的 Bean 才可在多线程环境下共享,Spring Bean 默认为 singleton 作用域。


那有状态 bean 呢?Spring 对一些如


  • RequestContextHolder

  • TransactionSynchronizationManager

  • LocaleContextHolder

  • ...


非线程安全状态 Bean 采用 ThreadLocal,让它们也成为线程安全的状态。


如用有状态 bean,也可使用 prototype 模式,每次在注入时,就重新创建一个 bean,在多线程中互不影响。


  1. Eic-server 所有的业务对象中的成员变量如:


  • Dao 中的 xxxDao

  • controller 中的 xxxService


都会被多个线程共享,那这些对象不会出现同步问题吗?比如造成 DB 插入,更新异常?


  1. 实体 bean,从客户端传递到后台 controller=》service=>Dao 流程中,他们这些对象都是单例的,那这些单例对象在处理我们的传递到后台的实体 bean 不会出问题吗?(实体 bean 在多线程中的解决方案)


因为实体 bean 不是单例的,他们并没有交给 Spring 管理!每次我们都手动的 New 出来的,如 BigObject bo = new BigObject(),所以即使是那些处理我们提交数据的业务处理类是被多线程共享,但他们处理的数据并不共享,数据是每个线程都有自己的一份,所以在数据方面不会出现线程安全问题。

对实体 bean 在多线程中的处理

  • 对实体 bean 一般通过方法参数的的形式传递(参数是局部变量),所以多线程间不会有影响

  • 有的地方对有状态的 bean 直接使用 prototype

  • 对使用 bean 的地方,可通过 new 创建


但那些:


  • 在 Dao 中的 xxxDao

  • controller 中的 xxxService


这些对象都是单例,那就不会出现线程同步问题。这些对象虽被多线程并发访问,可我们访问的是他们里面的方法,而这些类里面通常不会有成员变量。所以出问题的是系统里面的业务对象,务必注意这些业务对象里,千万不能有独立的成员变量,否则会出错。


所以我们在应用中的业务对象如下 controller 中的成员变量 List 和 paperService:



service 里的成员变量 ibatisEntityDao:



虽然这个应用有成员变量,但不会出现线程安全问题:


  • controller 里的成员变量 private TestPaperService papersService 之所以会成为成员变量,我们的目的是注入,将其实例化进而访问里面的方法

  • private static final int List = 0;是 final 的不会被改变

  • service 里面的 private IbatisEntityDao ibatisEntityDao;是框架本身的,线程同步问题已解决

spring 无状态的支持

Spring 框架对单例的支持是采用单例注册表。

spring 有状态的支持

spring 如何实现那些个有状态 bean,如 RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder]的线程安全,即使用 ThreadLocal 实现的


ThreadLocal

当使用 ThreadLocal 维护变量(仅是变量,因为线程同步问题就是成员变量的互斥访问出问题)时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每个线程都可独立改变自己的副本,而不会影响其它线程所对应副本。


从线程角度看,就好像每一个线程都完全拥有该变量,这其实就将共享变相为人手一份。虽使用 ThreadLocal 带来更多内存开销,但这点开销还微不足道。因为保存在 ThreadLocal 中的对象,通常较小。


initialValue(),protected 的方法,为子类重写而实现。该方法返回当前线程在该线程局部变量的初始值,是个延迟调用方法,在一个线程第 1 次调用 get()或 set(Object)时才执行且仅执行 1 次。

ThreadLocal 使用

要给线程初始化一个特殊值时,需要自己实现 ThreadLocal 的子类并重写该方法,通常使用一个内部匿名类对 ThreadLocal 进行子类化,EasyDBO 中创建 jdbc 连接上下文就是这样做的:



简单实现版本: 


ThreadLocal V.S synchronized

为保证多个线程对共享变量的安全访问,通常会使用 synchronized 保证同时只有一个线程对共享变量进行操作。但有些情况下,synchronized 不能保证多线程对共享变量的正确读写。


例如类有个类变量,该类变量会被多个类方法读写,当多线程操作该类的实例对象时,若线程对类变量有读取、写入操作就会发生类变量读写错误,即便是在类方法前加上 synchronized 也无效,因为同一个线程在两次调用方法之间时锁是被释放的,这时其它线程可访问对象的类方法,读取或修改类变量。这种情况下可以将类变量放到 ThreadLocal 中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。



多线程访问对于类变量和 ThreadLocal 变量的影响,QuerySvc 分别设置:


  • 类变量 sql

  • ThreadLocal 变量


使用时先创建 QuerySvc 的一个实例对象,然后产生多个线程,分别设置不同 sql 实例对象,再调用 execute,读取 sql 的值,看是否是 set 方法中写入的值。这类似 web 应用中多个请求线程携带不同查询条件对一个 servlet 实例的访问,然后 servlet 调用业务对象,并传入不同查询条件,最后要保证每个请求得到的结果是对应的查询条件的结果。


使用 QuerySvc 的工作线程如下:



运行线程:



先创建一个 QuerySvc 实例对象,然后创建若干线程来调用 QuerySvc 的 set 和 execute 方法,每个线程传入的 sql 都不一样,sql 变量中值不能保证在 execute 中值和 set 设置的值一样,在 web 应用中就表现为一个用户查询的结果不是自己的查询条件返回的结果,而是另一个用户查询条件的结果。


而 ThreadLocal 中的值总是和 set 中设置的值一样,这样通过使用 ThreadLocal 获得了线程安全性。

小结

若一个对象要被多个线程访问,而该对象存在类变量被不同类方法读写,为获得线程安全,可以用 ThreadLocal 替代类变量。


  • ThreadLocal 和线程同步机制相比有什么优势呢?

  • ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。


同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要分析:


  • 什么时候对变量进行读写

  • 什么时候需要锁定某个对象

  • 什么时候释放对象锁等繁杂的问题


而 ThreadLocal 为每个线程提供一个独立变量副本,隔离多线程对数据的访问冲突。ThreadLocal 采用“以空间换时间”。

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

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


一般 Web 应用划分为展现层、服务层和持久层三个层次,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。这就能根据需要,将一些非线程安全的变量以 ThreadLocal 存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。


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


发布于: 刚刚阅读数: 2
用户头像

JavaEdge

关注

正在征服世界的 Javaer。 2019.09.25 加入

曾就职于百度、携程、华为等大厂,阿里云开发者社区专家博主、腾讯云+社区2019、2020年度最佳作者、慕课网认证作者、CSDN博客专家,简书优秀创作者兼《程序员》专题管理员,牛客网著有《Java源码面试解析指南》。

评论

发布
暂无评论
Spring中的单例模式使用