Spring Top – Temp

Get started with Spring 5 and Spring Boot 2, through the reference Learn Spring course:

>> LEARN SPRING
Lightrun – Third Party Code
announcement - icon

Flakiness in REST requests is a common issue. A request can get a 200 OK in one scenario and a 409 next time. Sometimes a request can even succeed and fail intermittently on the same exact request. In short, working over HTTP can be a bit of a mess without solid tooling.

Also, while it’s easy enough to debug these issues locally when developing the application, we’re talking about production here - we can’t afford the downtime while you’re stepping in and out of code. Uptime is kind of the whole point.

With Lightrun, you can get the same level of access you get with a local debugger or profiler - no downtime required. You can add logs, metrics, and snapshots (think breakpoints, but without stopping the running service), in a safe and read-only manner - without redeploying, restarting, or even stopping the running service. Performance and security are maintained throughout the process.

Learn how to debug a live REST API (built with Spring, of course), using Lightrun, in this 5-minute tutorial:

>> Debugging REST Requests in Spring-Based applications using the Lightrun Platform

1. Overview

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.

2. Spring Beans and Java Heap Memory

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.

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?

As an example, let's take a Spring application that has a singleton bean called ProductService:

@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}.

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.

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:

Thread: pool-2-thread-1; bean instance: [email protected]; product id: 1 has the name: Product 1
Thread: pool-2-thread-2; bean instance: [email protected]; 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.

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.

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.

Next, we'll understand why it's crucial for singleton beans to be stateless.

4. Stateless Singleton Beans vs. Stateful Singleton Beans

To understand why stateless singleton beans are important, let's see what the side effects of using stateful singleton beans are.

Suppose we moved the productName variable to the class level:

@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: [email protected]; product id: 2 has the name: Product 2
Thread: pool-2-thread-1; bean instance: [email protected]; 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.

To avoid undesired side effects like this, it's crucial to keep our singleton beans stateless.

5. Conclusion

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.

As always, the code for these examples is available over on GitHub.

Spring bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> THE COURSE
Generic footer banner
9 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!