Spring Controller单例与线程安全那些事儿
目录
- 单例(singleton)作用域
- 原型(Prototype)作用域
- 多个HTTP请求在Spring控制器内部串行还是并行执行方法?
- 实现单例模式并模拟大量并发请求,验证线程安全
- 附录:Spring Bean作用域
单例(singleton)作用域
每个添加@RestController或@Controller的控制器,默认是单例(singleton),这也是Spring Bean的默认作用域。
下面代码示例参考了Building a RESTful Web Service,该教程搭建基于Spring Boot的web项目,源代码可参考spring-guides/gs-rest-service
GreetingController.java代码如下:
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;
}
}
我们使用HTTP基准工具wrk来生成大量HTTP请求。在终端输入如下命令来测试:
wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting
在服务端的标准输出中,可以看到类似日志。
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
日志中所有GreetingController实例的地址都是一样的,说明多个请求对同一个 GreetingController 实例进行处理,并且它的AtomicLong类型的counter字段正按预期在每次调用时递增。
原型(Prototype)作用域
如果我们在@RestController注解上方增加@Scope("prototype")注解,使bean作用域变成原型作用域,其它内容保持不变。
...
@Scope("prototype")
@RestController
public class GreetingController {
...
}
服务端的标准输出日志如下,说明改成原型作用域后,每次请求都会创建新的bean,所以返回的id始终是1,bean实例地址也不同。
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
多个HTTP请求在Spring控制器内部串行还是并行执行方法?
如果我们在greeting()方法中增加休眠时间,来看下每个http请求是否会串行调用控制器里面的方法。
@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); // 休眠1s
Greeting greet = new Greeting(counter.incrementAndGet(), String.format(template, name));
System.out.println("id=" + greet.getId() + ", instance=" + this);
return greet;
}
}
还是使用wrk来创建大量请求,可以看出即使服务端的方法休眠1秒,导致每个请求的平均延迟达到1.18s,但每秒能处理的请求仍达到166个,证明HTTP请求在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
实现单例模式并模拟大量并发请求,验证线程安全
单例类的定义: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();
}
}
测试单例类并创建大量请求并发调用:SingletonTest.java
package com.demo.designpattern;
import java.util.*;
import java.util.concurrent.*;
public class SingletonTest {
public static void main(String[] args) {
// 定义可返回计算结果的非线程安全的Callback实例
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());
// 定义可返回计算结果的线程安全的Callback实例(基于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();
}
}
附录:Spring Bean作用域
范围
|
描述 |
---|---|
singleton(单例) | (默认值)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 换句话说,当您定义一个bean并且其作用域为单例时,Spring IoC容器将为该bean所定义的对象创建一个实例。该单例存储在单例beans的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象。 |
prototype(原型) | 将单个bean定义的作用域限定为任意数量的对象实例。 每次对特定bean发出请求时,bean原型作用域都会创建一个新bean实例。也就是说,将Bean注入到另一个Bean中,或者您可以调用容器上的getBean()方法来请求它。通常,应将原型作用域用于所有有状态Bean,将单例作用域用于无状态Bean。 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个在单个bean定义后创建的bean实例。仅在web-aware的Spring ApplicationContext上下文有效。 |
session | 将单个bean定义的范围限定为HTTP Session的生命周期。仅在基于web的Spring ApplicationContext上下文有效。 |
application | 将单个bean定义的范围限定为ServletContext的生命周期。仅在基于web的Spring ApplicationContext上下文有效。 |
websocket | 将单个bean定义的作用域限定为WebSocket的生命周期。仅在基于web的Spring ApplicationContext上下文有效。 |
Spring Controller单例与线程安全那些事儿的更多相关文章
- 【转】Spring Bean单例与线程安全
一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...
- Spring Bean单例与线程安全
一.Spring单例模式及线程安全 Spring框架中的Bean,或者说组件,获取实例的时候都是默认单例模式,这是在多线程开发的时候需要尤其注意的地方. 单例模式的意思是只有一个实例,例如在Sprin ...
- SpringMVC Controller 单例 多例
对于SpringMVC 的Controller单例还是多例.下面举例说明:第一次:类是多例,类里包含一个普通属性,一个静态属性 结果:普通属性:0.............静态属性:0 普通属性:0. ...
- springmvc控制器controller单例问题
springmvc controller默认的是单例singleton的,具体可以查看注解scope可以一目了然. 单例的原因有二: 1.为了性能. 2.不需要多例. 1.这个不用废话了,单例不用每次 ...
- SpringMVC Controller单例和多例
对于SpringMVC Controller单例和多例,下面举了个例子说明下. 第一次:类是多例,一个普通属性和一个静态属性. 结果:普通属性:0.............静态属性:0 普通属性:0. ...
- Spring 获取单例流程(三)
读完这篇文章你将会收获到 Spring 何时将 bean 加入到第三级缓存和第一级缓存中 Spring 何时回调各种 Aware 接口.BeanPostProcessor .InitializingB ...
- Spring 获取单例流程(二)
读完这篇文章你将会收获到 Spring 中 prototype 类型的 bean 如何做循环依赖检测 Spring 中 singleton 类型的 bean 如何做循环依赖检测 前言 继上一篇文章 S ...
- 转:【Spring MVC Controller单例陷阱】
http://lavasoft.blog.51cto.com/62575/1394669/ Spring MVC Controller默认是单例的: 单例的原因有二:1.为了性能.2.不需要多例. 1 ...
- Spring MVC Controller单例陷阱
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://lavasoft.blog.51cto.com/62575/1394669 Spr ...
随机推荐
- 在 Ubuntu 14.04 安装 PyCharm 5 & Oracle JDK
由于授权问题,在较新的Linux发行版本中都不再包含Oracle Java,取而代之的是OpenJDK.Ubuntu也是如此. OpenJDK能满足大部分的应用程序运行条件,但PyCharm无法在Op ...
- 探索Kinect的更多可能——亲历第十九届机器人世界杯RoboCup
作者:微软亚洲研究院资深项目经理 吴国斌 2015年7月19日,第十九届RoboCup机器人世界杯足球赛,在中国合肥隆重开幕.来自全球七十六个国家和地区的一百余支代表队参加了决赛,他们优秀的作品给观众 ...
- 从TP-Link到雷蛇,纷纷入局智能手机业到底想干什么?
"眼看他起朱楼,眼看他宴宾客,眼看他楼塌了",这句形容世态炎凉的话其实与智能手机市场更为相像.诺基亚的辉煌与没落.黑莓的强势与消声无迹.摩托罗拉的数次易手.小米的横空出世与 ...
- 添砖加瓦:MySQL分布式部署
1.集群环境 管理节点(MGM):这类节点的作用是管理MySQLCluster内的其他节点,如提供配置数据,并停止节点,运行备份等.由于这类节点负责管理其他节点的配置,应该在启动其他节点之前启动这类 ...
- 目标用户偏好指数Target Group Index分析
目标用户偏好指数Target Group Index分析 TGI指数,全称Target Group Index,可以反映目标群体在特定研究范围内强势或者弱势. TGI指数计算公式 = 目标群体中具有某 ...
- 一文看懂js中元素偏移量(offsetLeft,offsetTop,offsetWidth,offsetHeight)
偏移量(offset dimension) 偏移量:包括元素在屏幕上占用的所有可见空间,元素的可见大小有其高度,宽度决定,包括所有内边距,滚动条和边框大小(注意,不包括外边距). 以下4个属性可以获取 ...
- 前端性能优化之Lazyload
前端性能优化之Lazyload @(Mob前端-冬晨)[JavaScript|技术分享|懒加载] [TOC] Lazyload 简介 前端工作中,界面和效果正在变得越来越狂拽炫酷,与此同时性能也是不得 ...
- 浅析SIEM、态势感知平台、安全运营中心
近年来SIEM.态势感知平台.安全运营中心等概念炒的火热,有的人认为这都是安全管理产品,这些产品就是一回事,有人认为还是有所区分.那么到底什么是SIEM.什么是态势感知平台.什么是安全运营中心,他们之 ...
- 脚本写一行echo也能写出bug ? glob了解一下
背景 最近处理一个 bug 很有意思,有客户反馈某个配置文件解析失败了,出错的那行的内容就只有一个字母 a. 最开始以为是谁改动了处理的脚本,但要到了问题代码中的脚本,比较发现跟库上是一样的. 又经过 ...
- Kona JDK 在腾讯大数据领域内的实践与发展
导语 | 近日,云+社区技术沙龙“腾讯开源技术”圆满落幕.本次沙龙邀请了多位腾讯技术专家,深度揭秘了腾讯开源项目TencentOS tiny.TubeMQ.Kona JDK.TARS以及Medical ...