1. Overview
1.概述
In this tutorial, we’ll learn how Spring beans created with the singleton scope work behind the scenes to serve multiple concurrent requests. Furthermore, we’ll understand how Java stores the bean instances in memory and how it handles concurrent access to them.
在本教程中,我们将学习使用singleton范围创建的Spring Bean如何在幕后工作,以服务于多个并发请求。此外,我们将了解Java如何在内存中存储Bean实例,以及如何处理对它们的并发访问。
2. Spring Beans and Java Heap Memory
2.Spring Bean和Java堆内存
The Java heap, as we know, is a globally shared memory accessible to all the running threads within an application. When the Spring container creates a bean with the singleton scope, the bean is stored in the heap. This way, all the concurrent threads are able to point to the same bean instance.
我们知道,Java 堆是一个全局共享的内存,可供应用程序中的所有运行线程访问。当Spring容器以单例范围创建Bean时,该Bean被存储在堆中。这样,所有并发的线程都能指向同一个Bean实例。
Next, let’s understand what the stack memory of a thread is and how it helps to serve concurrent requests.
接下来,让我们了解一下什么是线程的堆栈内存,以及它如何帮助服务于并发请求。
3. How Are Concurrent Requests Served?
3.如何提供并发请求?
As an example, let’s take a Spring application that has a singleton bean called ProductService:
作为一个例子,让我们来看看一个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;
}
}
This bean has a method getProductById() which returns product data to its callers. Further, the data returned by this bean is exposed to the clients on the endpoint /product/{id}.
这个Bean有一个方法getProductById(),它向其调用者返回产品数据。此外,该Bean返回的数据在端点 /product/{id}上暴露给客户端。
Next, let’s explore what happens at runtime when simultaneous calls hit the endpoint /product/{id}. Specifically, the first thread will call the endpoint /product/1 and the second thread will call /product/2.
接下来,让我们来探讨一下,当同时调用端点 /product/{id}时,在运行时发生了什么。具体来说,第一个线程将调用端点/product/1,第二个线程将调用/product/2。
Spring creates a different thread for each request. As we see in the console output below, both threads use the same ProductService instance to return the product data:
Spring为每个请求创建一个不同的线程。正如我们在下面的控制台输出中看到的,两个线程都使用相同的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
It’s possible for Spring to use the same bean instance in multiple threads, firstly because for each thread, Java creates a private stack memory.
Spring有可能在多个线程中使用同一个Bean实例,首先是因为Java为每个线程创建了一个私有的stack内存。
The stack memory is responsible for storing the states of the local variables used inside methods during thread execution. This way, Java makes sure that threads executing in parallel do not overwrite each other’s variables.
堆栈内存负责存储线程执行过程中方法内部使用的局部变量的状态。这样,Java确保并行执行的线程不会覆盖对方的变量。
Secondly, because ProductService bean sets no restrictions or locks at the heap level, the program counter of each thread is able to point to the same reference of the bean instance in the heap memory. Therefore, both threads can execute the getProdcutById() method simultaneously.
其次,由于ProductServicebean在堆级别没有设置任何限制或锁,每个线程的程序计数器能够指向堆内存中bean实例的同一个引用。因此,两个线程可以同时执行getProdcutById() 方法。
Next, we’ll understand why it’s crucial for singleton beans to be stateless.
接下来,我们将了解为什么单子Bean是无状态的,这一点至关重要。
4. Stateless Singleton Beans vs. Stateful Singleton Beans
4.无状态单子Bean与有状态单子Bean的对比
To understand why stateless singleton beans are important, let’s see what the side effects of using stateful singleton beans are.
为了理解为什么无状态单体Bean很重要,让我们看看使用有状态单体Bean的副作用是什么。
Suppose we moved the productName variable to the class level:
假设我们把productName变量移到类的层次。
@Service
public class ProductService {
private String productName = null;
// ...
public Optional getProductById(int id) {
// ...
productName = product.map(Product::getName).orElse(null);
// ...
}
}
Now, let’s run the service again and look at the output:
现在,让我们再次运行该服务,看看输出结果。
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
As we can see, the call for productId 1 shows the productName “Product 2” instead of “Product 1”. This happens because the ProductService is stateful and shares the same productName variable with all running threads.
我们可以看到,对productId 1的调用显示了productName“产品2 “而不是 “产品1″。这是因为ProductService是有状态的,并且与所有运行的线程共享同一个productName变量。
To avoid undesired side effects like this, it’s crucial to keep our singleton beans stateless.
为了避免像这样不受欢迎的副作用,保持我们的单子Bean无状态至关重要。
5. Conclusion
5.总结
In this article, we saw how concurrent access to singleton beans works in Spring. Firstly, we looked at how Java stores the singleton beans in the heap memory. Next, we learned how different threads access the same singleton instance from the heap. Finally, we discussed why having stateless beans is important, and looked at an example of what can happen if the beans are not stateless.
在这篇文章中,我们看到了Spring中对singleton beans的并发访问是如何进行的。首先,我们研究了Java如何在堆内存中存储单子Bean。接下来,我们了解了不同的线程如何从堆中访问同一个单子实例。最后,我们讨论了为什么拥有无状态Bean很重要,并看了一个例子,说明如果Bean不是无状态的,会发生什么。
As always, the code for these examples is available over on GitHub.
像往常一样,这些例子的代码可以在GitHub上找到over。