Spring单例bean如何处理并发请求

1.概述

了解使用单例范围创建的 Spring bean 如何在幕后工作以服务多个并发请求。此外,将了解 Java 如何将 bean 实例存储在内存中以及它如何处理对它们的并发访问。

2. Spring Beans 和 Java 堆内存

Java 堆是应用程序中所有正在运行的线程都可以访问的全局共享内存。**当 Spring 容器创建具有单例范围的 bean 时,该 bean 存储在堆中。**这样,所有并发线程都能够指向同一个 bean 实例。

3. 如何处理并发请求

举个例子,看一个 Spring 应用程序,它有一个名为ProductService的单例 bean :

@Service
public class ProductService {
    private final static List<Product> productRepository = asList(
      new Product(1, "Product 1", new Stock(100)),
      new Product(2, "Product 2", new Stock(50))
    );

    public Optional<Product> getProductById(int id) {
        Optional<Product> product = productRepository.stream()
          .filter(p -> p.getId() == id)
          .findFirst();
        String productName = product.map(Product::getName)
          .orElse(null);

        System.out.printf("当前线程: %s; bean实例: %s;商品id: %s 商品名字: %s%n", currentThread().getName(), this, id, productName);

        return product;
    }
}

这个 bean 有一个方法getProductById(),它将产品数据返回给它的调用者。此外,此 bean 返回的数据在端点 */product/{id}*上暴露给客户端。

接下来,探索当同时调用*/product/{id}时在运行时会发生什么。具体来说,第一个线程将调用端点/product/1*,第二个线程将调用*/product/2*。

Spring 为每个请求创建一个不同的线程。正如下面的控制台输出中看到的,两个线程都使用相同的ProductService实例来返回产品数据:

当前线程: pool-2-thread-1; bean实例: com.spring.demo.ProductService@18333b93; 商品id: 1 商品名字: Product 1
当前线程: pool-2-thread-2; bean实例: com.spring.demo.ProductService@18333b93; 商品id: 2 商品名字: Product 2

Spring 可以在多个线程中使用同一个 bean 实例,首先是因为对于每个线程,Java 都会创建一个私有堆栈内存

**堆栈内存负责存储线程执行期间方法内部使用的局部变量的状态。**这样,Java 确保并行执行的线程不会覆盖彼此的变量。

其次,由于ProductService bean 在堆级别没有设置任何限制或锁定,因此每个线程的程序计数器都能够指向堆内存中 bean 实例的相同引用。因此,两个线程可以同时执行getProdcutById()方法。

接下来,了解为什么单例 bean 无状态是至关重要的。

4. 无状态单例 Bean 与有状态单例 Bean

要了解为什么无状态单例 bean 很重要,看看使用有状态单例 bean 的副作用是什么。

假设将productName变量移至类级别:

@Service
public class ProductService {
    private String productName = null;
    
    // ...

    public Optional getProductById(int id) {
        // ...

        productName = product.map(Product::getName).orElse(null);

       // ...
    }
}

现在,再次运行服务并查看输出:

当前线程: pool-2-thread-2; bean实例: com.spring.demo.ProductService@18333b93; 商品id: 2 商品名字: Product 2
当前线程: pool-2-thread-1; bean实例: com.spring.demo.ProductService@18333b93; 商品id: 1 商品名字: Product 2

productId 1 的调用显示的是productName “Product 2”而不是“Product 1”。发生这种情况是因为ProductService是有状态的,并且与所有正在运行的线程共享相同的productName变量。

为了避免这样的不良副作用,保持单例 bean 无状态至关重要。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值