Catalog
- singleton scope
- Prototype Scope
- Does multiple HTTP requests execute serially or in parallel within the Spring Controller?
- Implement singleton mode and simulate a large number of concurrent requests to verify thread security
- Appendix: Spring Bean Scope
singleton scope
Each controller that adds @RestController or @Controller defaults to singleton, which is also the default scope for Spring Bean.
The following code example references Building a RESTful Web Service This tutorial builds a SpringBoot-based web project with source code for reference spring-guides/gs-rest-service
The GreetingController.java code is as follows:
package com.example.controller; import java.util.concurrent.atomic.AtomicLong; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class GreetingController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { Greeting greet = new Greeting(counter.incrementAndGet(), String.format(template, name)); System.out.println("id=" + greet.getId() + ", instance=" + this); return greet; } }
We use the HTTP benchmark tool wrk To generate a large number of HTTP requests.Enter the following command at the terminal to test:
wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting
Similar logs can be seen in the standard output on the server side.
id=162440, instance=com.example.controller.GreetingController@368b1b03 id=162439, instance=com.example.controller.GreetingController@368b1b03 id=162438, instance=com.example.controller.GreetingController@368b1b03 id=162441, instance=com.example.controller.GreetingController@368b1b03 id=162442, instance=com.example.controller.GreetingController@368b1b03 id=162443, instance=com.example.controller.GreetingController@368b1b03 id=162444, instance=com.example.controller.GreetingController@368b1b03 id=162445, instance=com.example.controller.GreetingController@368b1b03 id=162446, instance=com.example.controller.GreetingController@368b1b03
The address of all GreetingController instances in the log is the same, indicating that multiple requests process the same GreetingController instance and that its counter field of AtomicLong type is increasing as expected on each call.
Prototype Scope
If we add the @Scope("prototype") comment above the @RestController comment, the bean scope becomes the prototype scope, and the rest remains unchanged.
... @Scope("prototype") @RestController public class GreetingController { ... }
The standard output log on the server side is as follows, indicating that each request creates a new bean after changing to the prototype scope, so the returned id is always 1 and the bean instance address is different.
id=1, instance=com.example.controller.GreetingController@2437b9b6 id=1, instance=com.example.controller.GreetingController@c35e3b8 id=1, instance=com.example.controller.GreetingController@6ea455db id=1, instance=com.example.controller.GreetingController@3fa9d3a4 id=1, instance=com.example.controller.GreetingController@3cb58b3
Does multiple HTTP requests execute serially or in parallel within the Spring Controller?
If we increase the hibernation time in the greeting() method, see if each http request serially calls the method in the controller.
@RestController public class GreetingController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) throws InterruptedException { Thread.sleep(1000); // Hibernate 1s Greeting greet = new Greeting(counter.incrementAndGet(), String.format(template, name)); System.out.println("id=" + greet.getId() + ", instance=" + this); return greet; } }
Or using wrk to create a large number of requests, you can see that even though the server-side method hibernates for one second, resulting in an average delay of 1.18s per request, 166 requests can still be processed per second, proving that HTTP requests are a concurrent method of calling controllers instead of serial within Spring MVC.
wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting Running 10s test @ http://127.0.0.1:8080/greeting 12 threads and 400 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.18s 296.41ms 1.89s 85.22% Req/Sec 37.85 38.04 153.00 80.00% 1664 requests in 10.02s, 262.17KB read Socket errors: connect 155, read 234, write 0, timeout 0 Requests/sec: 166.08 Transfer/sec: 26.17KB
Implement singleton mode and simulate a large number of concurrent requests to verify thread security
Definition of a singleton class: Singleton.java
package com.demo.designpattern; import java.util.concurrent.atomic.AtomicInteger; public class Singleton { private volatile static Singleton singleton; private int counter = 0; private AtomicInteger atomicInteger = new AtomicInteger(0); private Singleton() { } public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } public int getUnsafeNext() { return ++counter; } public int getUnsafeCounter() { return counter; } public int getSafeNext() { return atomicInteger.incrementAndGet(); } public int getSafeCounter() { return atomicInteger.get(); } }
Test the case class and create a large number of requests with concurrent calls: SingletonTest.java
package com.demo.designpattern; import java.util.*; import java.util.concurrent.*; public class SingletonTest { public static void main(String[] args) { // Define a non-thread-safe Callback instance that returns the calculated results Callable<Integer> unsafeCallableTask = () -> Singleton.getSingleton().getUnsafeNext(); runTask(unsafeCallableTask); // unsafe counter may less than 1000, i.e. 984 System.out.println("current counter = " + Singleton.getSingleton().getUnsafeCounter()); // Define a thread-safe Callback instance that returns calculated results (based on AtomicInteger) Callable<Integer> safeCallableTask = () -> Singleton.getSingleton().getSafeNext(); runTask(safeCallableTask); // safe counter should be 1000 System.out.println("current counter = " + Singleton.getSingleton().getSafeCounter()); } public static void runTask(Callable<Integer> callableTask) { int cores = Runtime.getRuntime().availableProcessors(); ExecutorService threadPool = Executors.newFixedThreadPool(cores); List<Callable<Integer>> callableTasks = new ArrayList<>(); for (int i = 0; i < 1000; i++) { callableTasks.add(callableTask); } Map<Integer, Integer> frequency = new HashMap<>(); try { List<Future<Integer>> futures = threadPool.invokeAll(callableTasks); for (Future<Integer> future : futures) { frequency.put(future.get(), frequency.getOrDefault(future.get(), 0) + 1); //System.out.printf("counter=%s\n", future.get()); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } threadPool.shutdown(); } }
Appendix: Spring Bean Scope
Range |
describe |
---|---|
singleton | (Default) Limits the scope of a single bean definition for each Spring IoC container to a single object instance. In other words, when you define a bean and its domain is an instance, the Spring IoC container creates an instance of the object defined by the bean.The singleton is stored in the cache of the singleton beans and all subsequent requests and references to the named beans return the cached object. |
prototype | Limit the scope defined by a single bean to any number of object instances. Each time a request is made for a particular bean, the bean prototype scope creates a new bean instance.That is, inject a Bean into another Bean, or you can call the getBean() method on the container to request it.Typically, prototype scopes should be used for all stateful beans and singleton scopes for stateless beans. |
request | Limits the scope defined by a single bean to the life cycle of a single HTTP request.That is, each HTTP request has a bean instance created after a single bean definition.Valid only in the Spring ApplicationContext context of web-aware. |
session | Limit the scope of a single bean definition to the life cycle of an HTTP Session.Valid only in the web-based Spring ApplicationContext context. |
application | Limit the scope of a single bean definition to the life cycle of the ServletContext.Valid only in the web-based Spring ApplicationContext context. |
websocket | Limit the scope defined by a single bean to the lifetime of a WebSocket.Valid only in the web-based Spring ApplicationContext context. |