51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Spring 单例 Bean 如何处理并发请求?

1、Spring Bean 和 Java 堆内存 {#1spring-bean-和-java-堆内存}

Java 堆是一个全局共享内存,应用中的所有运行线程都可以访问它。当 Spring 容器创建 Singleton Scope Bean 时,该 Bean 将存储在堆中。这样,所有并发线程都能指向同一个 Bean 实例。

2、如何处理并发请求? {#2如何处理并发请求}

举例来说,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("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().getName(), this, id, productName);

        return product;
    }
}

该 Bean 有一个 getProductById() 方法,用于向调用者返回产品数据。此外,该 Bean 返回的数据将通过 /product/{id} 端点向客户公开。

接下来,让我们来看看运行时同时调用 /product/{id} 端点会发生什么情况。具体来说,第一个线程将调用 /product/1 端点,第二个线程将调用 /product/2

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

Thread: pool-2-thread-1; bean instance: com.baeldung.concurrentrequest.ProductService@18333b93; product id: 1 has the name: Product 1
Thread: pool-2-thread-2; bean instance: com.baeldung.concurrentrequest.ProductService@18333b93; product id: 2 has the name: Product 2

Spring 可以在多个线程中使用同一个 Bean 实例,这首先是因为 Java 会为每个线程创建一个私有栈内存。

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

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

接下来,来了解一下为什么 singleton bean 必须是无状态的?

3、无状态单例 Bean 与有状态单例 Bean {#3无状态单例-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);

       // ...
    }
}

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

Thread: pool-2-thread-2; bean instance: com.baeldung.concurrentrequest.ProductService@7352a12e; product id: 2 has the name: Product 2
Thread: pool-2-thread-1; bean instance: com.baeldung.concurrentrequest.ProductService@7352a12e; product id: 1 has the name: Product 2

可以看到,对 productId 1 的调用显示的 productNameProduct 2,而不是 Product 1。出现这种情况是因为 ProductService 是有状态的,它与所有运行线程共享同一个 productName 变量。

为了避免这种问题,让单例 Bean 保持无状态至关重要。

4、总结 {#4总结}

本文介绍了 Spring 中单例 Bean 是如何处理并发请求的,以及使用无状态单例 Bean 的重要性。


参考:https://www.baeldung.com/spring-singleton-concurrent-requests

赞(0)
未经允许不得转载:工具盒子 » Spring 单例 Bean 如何处理并发请求?