解析Spring中@Controller@Service等线程安全问题

目录
  • 首先问@Controller@Service是不是线程安全的?
  • 总结:

首先问@Controller @Service是不是线程安全的?

答:默认配置下不是的。为啥呢?因为默认情况下@Controller没有加上@Scope,没有加@Scope就是默认值singleton,单例的。意思就是系统只会初始化一次Controller容器,所以每次请求的都是同一个Controller容器,当然是非线程安全的。举个栗子:

@RestController
public class TestController {
	private int var = 0;
	@GetMapping(value = "/test_var")
	public String test() {
		System.out.println("普通变量var:" + (++var));
		return "普通变量var:" + var ;
	}
}

在postman里面发三次请求,结果如下:

普通变量var:1
普通变量var:2
普通变量var:3

说明他不是线程安全的。怎么办呢?可以给他加上上面说的@Scope注解,如下:

@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
public class TestController {
	private int var = 0;
	@GetMapping(value = "/test_var")
	public String test() {
		System.out.println("普通变量var:" + (++var));
		return "普通变量var:" + var ;
	}
}

这样一来,每个请求都单独创建一个Controller容器,所以各个请求之间是线程安全的,三次请求结果:

普通变量var:1
普通变量var:1
普通变量var:1

加了@Scope注解多的实例prototype是不是一定就是线程安全的呢?

@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
public class TestController {
	private int var = 0;
	private static int staticVar = 0;
	@GetMapping(value = "/test_var")
	public String test() {
		System.out.println("普通变量var:" + (++var)+ "---静态变量staticVar:" + (++staticVar));
		return "普通变量var:" + var + "静态变量staticVar:" + staticVar;
	}
}

看三次请求结果:

普通变量var:1---静态变量staticVar:1
普通变量var:1---静态变量staticVar:2
普通变量var:1---静态变量staticVar:3

虽然每次都是单独创建一个Controller但是扛不住他变量本身是static的呀,所以说呢,即便是加上@Scope注解也不一定能保证Controller 100%的线程安全。所以是否线程安全在于怎样去定义变量以及Controller的配置。所以来个全乎一点的实验,代码如下:

@RestController
@Scope(value = "singleton") // prototype singleton
public class TestController {
	private int var = 0; // 定义一个普通变量
	private static int staticVar = 0; // 定义一个静态变量
	@Value("${test-int}")
	private int testInt; // 从配置文件中读取变量
	ThreadLocal<Integer> tl = new ThreadLocal<>(); // 用ThreadLocal来封装变量
	@Autowired
	private User user; // 注入一个对象来封装变量
	@GetMapping(value = "/test_var")
	public String test() {
		tl.set(1);
		System.out.println("先取一下user对象中的值:"+user.getAge()+"===再取一下hashCode:"+user.hashCode());
		user.setAge(1);
		System.out.println("普通变量var:" + (++var) + "===静态变量staticVar:" + (++staticVar) + "===配置变量testInt:" + (++testInt)
				+ "===ThreadLocal变量tl:" + tl.get()+"===注入变量user:" + user.getAge());
		return "普通变量var:" + var + ",静态变量staticVar:" + staticVar + ",配置读取变量testInt:" + testInt + ",ThreadLocal变量tl:"
				+ tl.get() + "注入变量user:" + user.getAge();
	}
}

补充Controller以外的代码:
config里面自己定义的Bean:User

@Configuration
public class MyConfig {
	@Bean
	public User user(){
		return new User();
	}
}

我暂时能想到的定义变量的方法就这么多了,三次http请求结果如下:

先取一下user对象中的值:0===再取一下hashCode:241165852
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:241165852
普通变量var:2===静态变量staticVar:2===配置变量testInt:2===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:241165852
普通变量var:3===静态变量staticVar:3===配置变量testInt:3===ThreadLocal变量tl:1===注入变量user:1

可以看到,在单例模式下Controller中只有用ThreadLocal封装的变量是线程安全的。为什么这样说呢?我们可以看到3次请求结果里面只有ThreadLocal变量值每次都是从0+1=1的,其他的几个都是累加的,而user对象呢,默认值是0,第二交取值的时候就已经是1了,关键他的hashCode是一样的,说明每次请求调用的都是同一个user对象。
下面将TestController 上的@Scope注解的属性改一下改成多实例的:@Scope(value = "prototype"),其他都不变,再次请求,结果如下:

先取一下user对象中的值:0===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:2===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:3===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1

分析这个结果发现,多实例模式下普通变量,取配置的变量还有ThreadLocal变量都是线程安全的,而静态变量和user(看他的hashCode都是一样的)对象中的变量都是非线程安全的。也就是说尽管TestController 是每次请求的时候都初始化了一个对象,但是静态变量始终是只有一份的,而且这个注入的user对象也是只有一份的。静态变量只有一份这是当然的咯,那么有没有办法让user对象可以每次都new一个新的呢?当然可以:

public class MyConfig {
	@Bean
	@Scope(value = "prototype")
	public User user(){
		return new User();
	}
}

在config里面给这个注入的Bean加上一个相同的注解@Scope(value = "prototype")就可以了,再来请求一下看看:

先取一下user对象中的值:0===再取一下hashCode:1612967699
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:0===再取一下hashCode:985418837
普通变量var:1===静态变量staticVar:2===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:0===再取一下hashCode:1958952789
普通变量var:1===静态变量staticVar:3===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1

可以看到每次请求的user对象的hashCode都不是一样的,每次赋值前取user中的变量值也都是默认值0。

总结:

在@Controller/@Service等容器中,默认情况下,scope值是单例-singleton的,也是线程不安全的。

尽量不要在@Controller/@Service等容器中定义静态变量,不论是单例(singleton)还是多实例(prototype)他都是线程不安全的。

默认注入的Bean对象,在不设置scope的时候他也是线程不安全的。

一定要定义变量的话,用ThreadLocal来封装,这个是线程安全的

以上就是解析Spring中@Controller@Service等线程安全问题的详细内容,更多关于Spring@Controller@Service线程安全的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring Boot(三)之找回熟悉的Controller,Service

    找回熟悉的Controller,Service Controller哪儿去了? 对于很多习惯了Spring开发的同学来讲,Controller,Service,DAO 这些套路突然间都没了会有不适感.其实呢,这些东西还在,只不过对于较简单的情景下,这些都变成了系统背后帮你做的事情.这一小节我们就先来看看如何将Controller再召唤回来.召唤回来的好处有哪些呢?首先我们可以自定义API URL的路径,其次可以对参数和返回的json结构做一定的处理. 如果要让 TodoController 可以

  • springboot如何解决非controller类引用service的问题

    目录 解决非controller类引用service问题 解决办法 总的代码如下 在非controller层如何调用service层服务 通过init方法进行服务注入 调用方法形式如下 解决非controller类引用service问题 项目中遇到需要采用socket通信机制,由于这个方法没有写在控制类里面,调用service类的时候老是报java.lang.NullPointerException错误 解决办法 1.首先将@Autowired注解注入Service或者Mapper接口  @Au

  • 浅谈springboot如何保证多线程安全

    目录 如何保证多线程安全 1.springboot在多线程并发访问下是怎么做的 2.controller在多线程下如何尽可能保证线程安全,如何取舍 3.小结一下 单例模式与线程安全问题踩的坑 下面上一张该类的截图 现在说下解决方法 如何保证多线程安全 1.springboot在多线程并发访问下是怎么做的 我们在Controller下,一般都是@AutoWired一些Service,由于这些Service都交给了spring进行管理,因此他们单例的,对于在Controller中调用他们的方法,由于

  • spring根据controller中接收请求参数不同走不同service的实现方法

    前言 前几天一个工程中,需要实现这样一个场景:根据前端发送过来的请求参数的不同,走不同的 service(可同事走多个),最初我的思路是尝试实现在 spring 中实现动态的依赖注入,也就是根据请求参数,动态的在 controller 中注入某个 service 接口的特定实现(接口有多个实现),但是发现这个实现不了,然后想了想,换了个思路,重新设计了一下,实现了需求中的场景. 附controller的分类: 正文 我的解决办法是,使用"生产线工人工作能力自己掂量机制"来解决,这名字我

  • 解析Spring中@Controller@Service等线程安全问题

    目录 首先问@Controller@Service是不是线程安全的? 总结: 首先问@Controller @Service是不是线程安全的? 答:默认配置下不是的.为啥呢?因为默认情况下@Controller没有加上@Scope,没有加@Scope就是默认值singleton,单例的.意思就是系统只会初始化一次Controller容器,所以每次请求的都是同一个Controller容器,当然是非线程安全的.举个栗子: @RestController public class TestContro

  • Spring中Controller应用深入理解

    目录 概述 1. 添加依赖 2. 关于异常 总结 概述 Controller是Spring接受并处理网页请求的组件,是整个应用的入口,因此学会Controller的常用注解对理解一个应用是重中之重.SpringBoot的Controller中经常会用到注解@Controller.@RestController.@RequestMapping.@RequestBody等,本短文主要对这些常用的Controller注解进行简单介绍. 1. 添加依赖 首先,需要保障此项目中存在spring-boot-

  • 解析Spring中的静态代理和动态代理

    一.静态代理 1.1.静态代理的使用 静态代理,代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用,这样,当我们需要调用被代理类的方法时,可以通过调用代理类的方法来做到.举例如下: 假设领导的工作是开会和给员工考评. 先定义一个接口: package com.sharpcj; public interface IWork { void meeting(); int evaluate(String name); } 然后定义领导类: package com.sharpcj; impo

  • 解析Spring中面向切面编程

    一.AOP--另一种编程思想 1.1.什么是 AOP AOP (Aspect Orient Programming),直译过来就是 面向切面编程.AOP 是一种编程思想,是面向对象编程(OOP)的一种补充.面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面. 从<Spring实战(第4版)>图书中扒了一张图: 从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块. 1.2.为什么需要 AOP 想象下面的场景,开发中在多个模块间有

  • 面试Spring中的bean线程是否安全及原因

    目录 SpringBean作用域 spring单例,为什么controller.service和dao确能保证线程安全? 总结 面试官经常喜欢问Spring中的bean是不是线程安全的这个问题用来考察对Spring  中Bean作用域的理解,先说结论,Spring中的Bean不是线程安全的. Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究. Spr

  • Java 单例模式线程安全问题

    Java 单例模式线程安全问题 SpringIOC容器默认提供bean的访问作用域是单例模式.即在整个application生命周期中,只有一个instance.因此在多线程并发下,会有线程安全风险.我们在MVC框架下的servlet就是线程安全的.由于该servlet是在客户端,多并发相对少,但是对于web service端,需要考虑到. ThreadLocal类:为每一个线程提供了一个独立的变量(实例)副本,从各将各个不同的实例访问isolation. 在同步锁机制中,后来者线程等待先行线程

  • Struts中action线程安全问题解析

    [问题描述] 最近公司安排我面试Java的FreshMan,面试者一般是工作1年多点的新人(这里我就装老一下,其实我也才工作3年不到),在被问及Struts1和Struts2的Action的线程安全问题的时候,大多是支支吾吾,答不出所以然.所以在这里我整理一下我个人的理解. [问题答案] 这是由于Servlet的工作原理产生的.我们先来简单回顾一下Servlet的生命周期"初始化->init->service->destroy->卸载". 这里大家都知道,我们在

  • 基于Spring中的线程池和定时任务功能解析

    1.功能介绍 Spring框架提供了线程池和定时任务执行的抽象接口:TaskExecutor和TaskScheduler来支持异步执行任务和定时执行任务功能.同时使用框架自己定义的抽象接口来屏蔽掉底层JDK版本间以及Java EE中的线程池和定时任务处理的差异. 另外Spring还支持集成JDK内部的定时器Timer和Quartz Scheduler框架. 2.线程池的抽象:TaskExecutor TaskExecutor涉及到的相关类图如下: TaskExecutor接口源代码如下所示: p

  • Spring5源码解析之Spring中的异步和计划任务

    Java提供了许多创建线程池的方式,并得到一个Future实例来作为任务结果.对于Spring同样小菜一碟,通过其scheduling包就可以做到将任务线程中后台执行. 在本文的第一部分中,我们将讨论下Spring中执行计划任务的一些基础知识.之后,我们将解释这些类是如何一起协作来启动并执行计划任务的.下一部分将介绍计划和异步任务的配置.最后,我们来写个Demo,看看如何通过单元测试来编排计划任务. 什么是Spring中的异步任务? 在我们正式的进入话题之前,对于Spring,我们需要理解下它实

随机推荐