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