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具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
  • 方式三:使用scope=“prototype”,创建多个实例