Spring Bean的作用域

633 阅读3分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

前言

前面说了Spring Ioc的核心容器的启动的过程, IOC生命周期, 本文介绍一下Bean的作用域,以及Spring是如何保证线程安全的.

Bean 作用域相关的问题

1. Spring支持的几种Bean的作用域

Spring Bean作用域.png

注意:  缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

2. Spring框架中的单例bean是线程安全的吗?

不是线程安全的. spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。

实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

注意: 1. 有状态就是有数据存储功能。2. 无状态就是不会保存数据。所谓线程安全其实也主要是因为有共享资源,共享资源变更导致线程不安全.

3. Spring如何处理线程并发问题?

既然Bean不是线程安全的,那么Spring是如何处理线程并发问题的呢?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

3.1 场景举例

Spring 并发Bean的安全,看下面的代码

@Service
public class GenerateServiceImpl {

    @Transactional(rollbackFor = Exception.class)
    public void generate(Object req) {

        // 插入一条记录
        insertRecord(req);
        // 业务逻辑处理
        doSomeThing();
        // 更新状态
        updateTaskStatus(req);
    }
    private void updateTaskStatus(Object req) {
        //...
    }
    private void doSomeThing() {
        //....
    }
    private void insertRecord(Object req) {
        //...
    }
}

我们知道每次DB操作都需要一个Connection连接, 那么insertRecord 和 updateTaskStatus 需要两次链接DB, 那么Spring 是如何保证在同一个事务中的呢?

DataSourceTransactionManager#doBegin

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   Connection con = null;
   // ....

     // Bind the connection holder to the thread.
      if (txObject.isNewConnectionHolder()) {
         TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
        
   // ...

}

TransactionSynchronizationManager#bindResource

image.png

image.png

其实就是用ThreadLocal<Map<Object, Object>> 数据结构保证最终取的Connection是同一个实例.

4. 怎样定义类的作用域?

它可以通过bean 定义中的scope属性来定义。默认 singleton.