Spring 的 Controller 是单例还是多例?

2,114 阅读4分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动

作者的其他平台:

| CSDN:blog.csdn.net/qq_4115394…

| 掘金:juejin.cn/user/651387…

| 知乎: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工具分析优化器执行计划

几个必须掌握的SQL优化技巧(五):Show Profile分析SQL性能

几个必须掌握的SQL优化技巧(六):针对SQL语句的优化