小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
作者的其他平台:
| CSDN:blog.csdn.net/qq_4115394…
| 知乎:www.zhihu.com/people/1024…
| GitHub:github.com/JiangXia-10…
| 公众号:1024笔记
本文大概2681字,读完共需7分钟
1 前言
在笔试面试的时候经常会遇到的一个问题:Spring 的 Controller 是单例还是多例?
首先答案是:controller默认是单例的,不要使用非静态的成员变量,否则会发生数据逻辑混乱。而且正因为单例,所以它也不是线程安全的。
那么既然不是线程安全的,那么spring怎么保证做到并发的安全性呢?
2 正文
首先我们来看下面的例子:
package com.springboot.springbootdemo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ScopeController {
public int number = 0;
@RequestMapping("/text1")
public int test1(){
number=number+1;
return number;
}
@RequestMapping("/text2")
public int test2(){
number=number+1;
return ++number;
}
}
复制代码
可以发现我们首先访问 http://localhost:8080/text1,得到的答案是1;然后我们再访问 http://localhost:8080/text2,得到的答案是 2。所以得到的不同的值,证明这是线程不安全的。
说到这个问题,我们就需要说到之前提到的一个问题,那就是关于spring的bean作用域,关于spring的作用域可以参考之前的一篇文章:传送门Spring注解(三):@scope设置组件作用域。
在Spring注解开发中@Scope注解可以用于设置组件的作用域,通过@Scope源码,可以发现@Scope注解有五种作用域,即:
SINGLETON:单例模式,默认模式,不写的时候默认是SINGLETON
PROTOTYPE:原型模式
REQUEST:同一次请求则只创建一次实例
SESSION:同一个session只创建一次实例
GLOBAL SESSION:全局的web域,类似于servlet中的application。
复制代码
在默认的情况下,controller是单例模式singleton,单例是不安全的,因为它会导致属性重复使用。
接下来我们再来给controller增加作用多例 @Scope("prototype")
package com.springboot.springbootdemo.controller;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Scope("prototype")
public class ScopeController {
public int number = 0;
@RequestMapping("/text1")
public String test1(){
number=number+1;
return String.valueOf(number);
}
@RequestMapping("/text2")
public String test2(){
number=number+1;
return String.valueOf(number);
}
}
复制代码
可以发现我们首先访问 http://localhost:8080/text1,得到的答案是1;然后我们再访问 http://localhost:8080/text2,得到的答案是 1。这时候就是线程安全的了。
3 总结
通过上面的例子,可以得出结论:
1、spring的controller默认是单例模式,而这种模式下是线程不安全的,所以在这种模式下不要在controller种定义成员变量;
2、在单例模式下可以通过使用ThreadLocal 解决线程安全问题。
线程安全问题主要是全局变量和静态变量引起的。若每个线程中对全局变量、静态变量读操作,而无写操作,一般来说这个全局变量是线程安全的。若多个线程同时执行写操作,需要考虑线程同步问题,否则影响线程安全。spring 使用ThreadLocal 实现高并发下 共享资源的同步。
使用ThreadLocal 为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线 程都完全拥有该变量。
ThreadLocal 为每一个变量维护变量的副本的原理如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
复制代码
3、通过@Scope(“prototype”)注解,将默认的作用域改为多例模式,这时候就能够在controller种定义非静态的成员变量了。
在spring的controller默认是单例,原因有两点:
(1)为了性能:单例不用每次都创建
(2)不需要多例:只要controller中不定义属性,那么单例完全是安全可用的,如果定义了,那单例肯定会出现竞争访问;非要定义,则通过注解@Scope("prototype"),将其设置为多例模式。
今日推荐
几个必须掌握的SQL优化技巧(一):查看SQL语句的执行频率
几个必须掌握的SQL优化技巧(二):如何定位低效率执行SQL
几个必须掌握的SQL优化技巧(三):Explain分析执行计划
几个必须掌握的SQL优化技巧(四):使用Trace工具分析优化器执行计划