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(&quot;Thread: %s; bean instance: %s; product id: %s has the name: %s%n&quot;, 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 的调用显示的 productName 是 Product 2,而不是 Product 1。出现这种情况是因为 ProductService 是有状态的,它与所有运行线程共享同一个 productName 变量。
为了避免这种问题,让单例 Bean 保持无状态至关重要。
4、总结 {#4总结}
本文介绍了 Spring 中单例 Bean 是如何处理并发请求的,以及使用无状态单例 Bean 的重要性。
参考:https://www.baeldung.com/spring-singleton-concurrent-requests
 51工具盒子
51工具盒子 
                 
                             
                         
                         
                         
                        