Spring容器中的Bean是否线程安全?

Spring容器中的Bean是否线程安全?
  • 前言

    • Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但还是要结合具体的Bean的Scope(作用域)来分析。
  • 首先我们先来了解Bean的作用域

    • 单例(singleton):(默认)每一个Spring IoC容器都拥有唯一的一个实例对象
    • 原型(prototype):一个Bean定义,任意多个对象
    • 请求(request):一个HTTP请求会产生一个Bean对象,也就是说,每一个HTTP请求都有自己的Bean实例。只在基于web的Spring ApplicationContext中可用
    • 会话(session):限定一个Bean的作用域为HTTPsession的生命周期,不同的会话使用不同的实例。同样,只有基于web的Spring ApplicationContext才能使用
    • 全局会话(global session):限定一个Bean的作用域为全局HTTPSession的生命周期。通常用于门户网站场景,同样,只有基于web的Spring ApplicationContext可用
  • 什么是单例,什么是多例?

    • 单例:是指某个类只有一个实例,向整个系统提供这个实例。比如说单例模式
    • 多例:是指针对每一次请求都来创建一个新的对象(GC:不当人啊!)
  • 单例的好处:

    • 减少请求时候创建对象的开销,提升系统性能。少干一些活儿,效率自然就高了。
    • 减少对象的创建,对JVM垃圾回收友好。
  • 单例的坏处:

    • 对于有状态的变量可能会造成线程安全问题,因为只有一个实例,如果操作的是有状态的全局变量,多个线程之间可能会操作同一个变量和对象导致线程不安全问题
    • 什么是有状态的变量或者Bean?
    • Bean的状态:
      • 有状态对象(Stateful Bean):就是有实例变量的对象,可以保存数据,是非线程安全的。
      • 无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是线程安全的。通常这样的对象只是有一些接口,起到桥接的作用。
  • 多例的好处:

    • 线程安全,每个请求过来都分配一个新的对象,里面的所有东西都是该线程独享的
  • 单例的坏处:

    • 资源开销大,每一次请求都要来创建对象,消耗性能
    • 会产生大量的垃圾对象,消耗内存
Bean的线程安全问题
  • 线程安全问题主要是全局变量静态变量引起的。若每个线程中对全局变量、静态变量读操作,而无写操作,一般来说这个全局变量是线程安全的。若多个线程同时执行写操作,需要考虑线程同步问题,否则影响线程安全。

  • 关于Bean的线程安全问题,要从单例与多实例Bean分别进行说明。

  • 多实例Bean每次都会创建一个新的对象,线程之间并不存在Bean共享,自然不会有线程安全问题

  • 对于单例Bean又得区分有状态Bean和无状态Bean(前面有所提及)。单例Bean,所有线程都共享一个Bean实例,因此是存在资源竞争的。

    • 对于有状态的Bean

    • 方式一:加同步锁Synchronized,高并发情况下会有性能问题,不推荐

    • 方式二:我们可以用ThreadLocal将实例属性包起来

      package com.xwfu.test;
      
      import java.text.DateFormat;
      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      
      public class Demo {
         private static ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
              @Override
              protected DateFormat initialValue() {
                  return new SimpleDateFormat("yyyy-MM-dd");
              }
          };
      
          public static Date parseDate(String dateStr) throws ParseException {
              return df.get().parse(dateStr); //每次取DateFormat这个对象的时候 都是从本地线程中取的,变量副本
          }
      
          public static String format(Date date) {
              return df.get().format(date); //每次取DateFormat这个对象的时候 都是从本地线程中取的,变量副本
          }
      }
      
      
    • ThreadLocal与synchronized有本质的区别:

      • Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
      • Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
      • ThreadLocal是通过给每个线程创建一份单独的存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
      • 关于ThreadLocal:推荐博文https://blog.csdn.net/u010445301/article/details/111322569
    • 方式三:使用scope=“prototype”,创建多个实例

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值