SpringMVC中Controller为什么能够处理并发访问?Springboot中的定时任务是否会发生阻塞?

文章目录

  • SpringMVC中Controller为什么能够处理并发访问?
    • 当多个请求同时访问服务器的时候
    • Controller、Service、DAO是线程安全的吗?
      • 关于类中的变量
    • Controller、Service、DAO等类都默认为单例模式
    • Controller、Service、DAO等类中的方法当中的并发问题
    • 关于DAO并发访问数据的问题
    • Controller不是线程安全的(单例,存在成员变量时出现线程安全问题)
    • Controller并发安全的解决办法
    • 关于controller的一点疑问
  • Springboot中的定时任务是否会发生阻塞?

SpringMVC中Controller为什么能够处理并发访问?

两个线程同时请求controller

SpringMVC中用来处理http请求的Controller是基于Servlet实现的,Spring中绝大多数的类都是单例的,Servlet也是这样。

Controller、Service、DAO都是默认单例模式

既然Controller是单例模式,那么它是怎么能够在同时处理很多个请求的呢?

想要搞明白这点,首先面临的一个问题是:计算机是如何处理一个请求的呢?

计算机大部分的任务都是由CPU来完成的,Controller虽然叫做控制器,但是实际上执行处理任务的角色是CPU。控制器只是提供了CPU处理请求的方法,所以实际上是CPU根据Controller中的代码来处理。

那么是谁来控制CPU来进行任务呢?当然是进程了,在我们面对的计算机中,进程是运行的基本单元。

所以计算机是如何处理一个请求的呢?请求是由计算机中某个进程根据特定的指令来处理的。

根据这一点,我们可以知道,当服务器收到一个请求后,会有一个进程来处理它,把这个请求经过拦截器等等不同的处理程序,终于来到了控制器了,控制器对它进行了一些处理,然后又把它交给下一步的程序处理(实际上的实施主体是进程),经过一些处理,这时就可以叫处理过后的数据为响应了,进程把这些数据发送到某个接受的地方,一次Http请求就完成了。

在这个过程中,真正操作的是一个进程,代码是存放在内存中的一段一段数据,进程从中读取数据,也许会对其中的某些数据进行修改(这里就涉及到了多线程的安全问题)。

而这一次处理请求并返回响应的过程,在实际中操作的是一个线程,它在主进程中创建,用于处理一个请求。

当多个请求同时访问服务器的时候

现在,有多个请求同时访问服务器,每个请求都有一个线程来处理,线程由服务器程序来创建(例如SpringBoot默认使用的Tomcat),线程根据内存中的代码(代码相当于说明书)执行下去,每个线程都可以访问到Controller中的代码,如果Controller只有一个的话,那每个线程都访问这个Controller,根据它的代码来执行。代码就像是一份说明书,无论多少的请求,都按照同一份说明书来处理。

知道了每个请求都是由一个线程来处理,我们也就可以明白一个服务器同时能够处理的请求数与它的线程数有很大的关系。线程的创建是比较消耗资源的,所以容器一般维持一个线程池。像Tomcat的线程池 maxThreads 是200, minSpareThreads 是25。实际中单个Tomcat服务器的最大并发数只有几百,部分原因就是只能同时处理这么多线程上的任务。当然,并发的限制肯定不止在这里,还有很多需要考虑的地方。

因此,应对请求分配线程处理的是servlet容器(也就是tomcat等服务器程序)。

Controller、Service、DAO是线程安全的吗?

关于类中的变量

首先要先说一下几个基本概念:
1、静态变量:线程非安全。
静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。

2、实例变量:单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。
实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。

3、局部变量:线程安全。
每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题

Controller、Service、DAO等类都默认为单例模式

我们知道, Controller、Service、DAO都是默认为单例模式的,

又因为,如果一个类无论什么时候都不会改变,那么它就是线程安全的,无论多少线程同时访问,都会得到相同的结果,不会有任何影响,不用考虑多线程带来的影响。

所以在Controller、Service、DAO尽量使用局部变量,不要使用类的成员变量,如果使用的话,记得一定要加锁。

控制器中如果没有维持可变的成员变量,也类似于不可变类,它在多线程情况下也不需要多考虑,和在单线程下区别不大,当然这一般不会发生。我们经常在其中定义许多Service,在容器启动的时候这些Service被注入进来,用户传入的请求大部分在这里和服务器进行交互,比如查看当前是否登录,请求查看用户信息等等,根据Controller中的代码,调用不同的Service对这些信息进行处理。这里就要考虑到线程安全的问题了。

Controller、Service、DAO等类中的方法当中的并发问题

尽管 Controller、Service、DAO都是默认为单例模式的,

但是每个方法在调用栈里都会有自己独立的栈帧,每个栈帧里都有对应方法需要的参数和返回地址。当调用方法时,会创建新的栈帧,并压入调用栈;当方法返回时,对应的栈帧就会被自动弹出。

栈帧是在调用方法时创建,方法返回时“消亡”。

局部变量存放在哪里?
局部变量的作用域在方法内部,当方法执行完,局部变量也就没用了。可以这么说,方法返回时,局部变量也就“消亡”了。此时,我们会联想到调用栈的栈帧。没错,局部变量就是存放在调用栈里的。此时,我们可以将方法的调用栈用下图表示。

线程封闭
方法里的局部变量,因为不会和其他线程共享,所以不会存在并发问题。这种解决问题的技术也叫做线程封闭。仅在单线程内访问数据。由于不存在共享,所以即使不设置同步,也不会出现并发问题。

所以在Controller、Service、DAO尽量使用局部变量,不要使用类的成员变量,如果使用的话,记得一定要根据业务逻辑来判断是否要加锁。

关于DAO并发访问数据的问题

假设一个例子,现在要做一个用户注册服务,用户注册需要绑定手机号码,因此,注册的业务逻辑中必须要有一个判断,也就是判断该手机号码有没有被注册,这需要DAO层去数据库查询是否有拥有该手机号码的记录。

现在有两个注册请求同时发出,带着一样的电话号码。这两个请求同时到达Tomcat服务器,在两个线程内同时调用Controller,在DAO层查询电话号码的结果都是“该手机号码没有被注册”,于是都用该电话号码进行了注册,但是由于数据库库中,用户表中,电话号码这个属性被设置了unique key,所以这两个注册请求一定会有一个请求发生异常,因此,做这种功能的时候一定要加上异常处理。




**单例模式(Singleton)**是程序设计中一种非常重要的设计模式,设计模式也是Java面试重点考察的一个方面。面试经常会问到的一个问题是:SpringMVC中的Controller是单例还是多例,很多同学可能会想当然认为Controller是多例,其实不然。

Tomcat官网截图如下:
在这里插入图片描述

根据Tomcat官网中的介绍,对于一个浏览器请求,tomcat会指定一个处理线程,或是在线程池中选取空闲的,或者新建一个线程。

Each incoming request requires a thread for the duration of that request. If more simultaneous requests are received than can be handled by the currently available request processing threads, additional threads will be created up to the configured maximum (the value of the maxThreads attribute). If still more simultaneous requests are received, they are stacked up inside the server socket created by the Connector, up to the configured maximum (the value of the acceptCountattribute). Any further simultaneous requests will receive “connection refused” errors, until resources are available to process them.

—— https://tomcat.apache.org/tomcat-7.0-doc/config/http.html

在Tomcat容器中,每个servlet是单例的。**在SpringMVC中,Controller 默认也是单例。**采用单例模式的最大好处,就是可以在高并发场景下极大地节省内存资源,提高服务抗压能力。

单例模式容易出现的问题是:在Controller中定义的实例变量,在多个请求并发时会出现竞争访问,Controller中的实例变量不是线程安全的。

Controller不是线程安全的(单例,存在成员变量时出现线程安全问题)

正因为Controller默认是单例,所以不是线程安全的。如果用SpringMVC 的 Controller时,尽量不在 Controller中使用实例变量,否则会出现线程不安全性的情况,导致数据逻辑混乱。

举一个简单的例子,在一个Controller中定义一个非静态成员变量 num 。通过Controller成员方法来对 num 增加。

@Controller
public class TestController {private int num = 0;@RequestMapping("/addNum")public void addNum() {System.out.println(++num);}
}

在本地运行后:

  • 首先访问 http:// localhost:8080 / addNum,得到的答案是1;
  • 再次访问 http:// localhost:8080 / addNum,得到的答案是 2。
  • 两次访问得到的结果不同,num已经被修改,并不是我们希望的结果,接口的幂等性被破坏。

从这个例子可以看出,所有的请求访问同一个Controller实例,Controller的私有成员变量就是线程共用的。某个请求对应的线程如果修改了这个变量,那么在别的请求中也可以读到这个变量修改后的的值。

Controller并发安全的解决办法

如果要保证Controller的线程安全,有以下解决办法:

  • 尽量不要在 Controller 中定义成员变量
  • 如果必须要定义一个非静态成员变量,那么可以通过注解 @Scope(“prototype”),将Controller设置为多例模式。
@Controller
@Scope(value="prototype")
public class TestController {private int num = 0;@RequestMapping("/addNum")public void addNum() {System.out.println(++num);}
}

Scope属性是用来声明IOC容器中的对象(Bean)允许存在的限定场景,或者说是对象的存活空间。在对象进入相应的使用场景之前,IOC容器会生成并装配这些对象;当该对象不再处于这些使用场景的限定时,容器通常会销毁这些对象。

Controller也是一个Bean,默认的 Scope 属性为Singleton,也就是单例模式。如果Bean的 Scope 属性设置为 prototype 的话,容器在接受到该类型对象的请求时,每次都会重新生成一个新的对象给请求方。

  • Controller 中使用 ThreadLocal 变量。每一个线程都有一个变量的副本。
public class TestController {private int num = 0;private final ThreadLocal <Integer> uniqueNum =new ThreadLocal <Integer> () {@Override protected Integer initialValue() {return num;}};@RequestMapping("/addNum")public void addNum() {int unum = uniqueNum.get();uniqueNum.set(++unum);System.out.println(uniqueNum.get());}
}

以上代码运行以后,每次请求 http:// localhost:8080 / addNum , 得到的结果都是1。

更严格的做法是用AtomicInteger类型定义成员变量,对于成员变量的操作使用AtomicInteger的自增方法完成。

总的来说,还是尽量不要在 Controller 中定义成员变量为好。




关于controller的一点疑问

一般情况下,spring中的controller是单例模式的,也就是说所有的访问都会调用同一个controller的方法,自然而然的就会想到并发的问题,当某一个请求调用controller方法尚未退出时,是否会造成后续请求的阻塞。
写个小demo测试一下

	@RequestMapping("/dotest01/{id}")@ResponseBodypublic String dotest01(@PathVariable("id") int id) {long start =System.currentTimeMillis();String str;if(id==1) {try {Thread.sleep(4000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}log.info("1号"+Thread.currentThread().getName());log.info(this.toString());str="1号结果!";}else {log.info("2号"+Thread.currentThread().getName());log.info(this.toString());str="2号结果";}long time =System.currentTimeMillis()-start;return str+time;}

进行测试先发送访问dotest01/1,后访问dotest01/2,很明显第一个页面尚在加载,第二个就已经返回
在这里插入图片描述在这里插入图片描述
再看看后头的对象
在这里插入图片描述
很明显,线程是单独的但是controller是同一个。
可以确认的是,不用考虑controller的阻塞问题,再写一个多线程测试案例

public class TestThread {public static void main(String[] args) {MyTool tool = new MyTool();new Thread(new MyThread(0, tool)).start();new Thread(new MyThread(1, tool)).start();}
}class MyThread implements Runnable {private int id;private MyTool tool;public MyThread(int id,MyTool tool) {this.id=id;this.tool=tool;}public void run() {long start = System.currentTimeMillis();System.out.println("Thred:"+Thread.currentThread().getName()+"=="+tool.toString());tool.dosome(id);long time = System.currentTimeMillis()-start;System.out.println("id"+id+"==time="+time);}
}class MyTool {public void dosome(int id) {if(id ==0) {System.out.println("00开始工作!");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("00工作完成!");}else {System.out.println("01开始工作!");System.out.println("01工作完成!");}		}
}

测试结果为在这里插入图片描述
符合期望,工作线程不同,工作对象同一个。在修改一下

public synchronized void dosome(int id){.....
}

为方法加上锁,这时候就不同了,在这里插入图片描述
此时线程才会阻塞,说到底对阻塞的概念有点混乱,多线程是可以同时访问通一个不加锁方法,所谓额并发问题是多线程操作造成的数据混乱,阻塞是加锁造成的,很显然controller并未给方法加锁,所以并不会有阻塞的问题。
但是也得注意,在controller中创建全局变量这时候就要考虑并发问题了。




Springboot中的定时任务是否会发生阻塞?

Springboot中一个定时任务没执行完,是否会影响下一个定时任务执行?

在springboot中使用定时任务的步骤

1.在启动类上加上注解:@EnableScheduling,表示允许定时任务执行

2.定时任务需要在类上加上@Component或者其衍生类(Controller、Service等),用于纳入Spring容器管理。

3.在需要定时任务方法上增加注解@Scheduled,注解的参数是定时任务执行时机

首先需要知道:定时任务默认是单线程的。所以默认情况下,上一个定时任务没有执行完,下一个定时任务是不会开始的

结论:
1.定时任务默认是单线程的。如果任务执行时间超过定时任务间隔时间,不管是同一个定时任务还是不同的定时任务,下一个任务都会被阻塞。
2.实现SchedulingConfigurer接口后,定时任务会变成多线程执行。不同的定时任务之间互不影响,同一个定时任务(方法)依然会有被阻塞的机制。
3.如果定时任务交给线程池处理,则下一个任务不会被阻塞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/554802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java进阶 - 易错知识点整理

转载&#xff1a;https://blog.csdn.net/qq_33934427/article/details/125903960 文章目录1、JavaEE2、网络基础3、Mysql4、Spring/SpringMVC&#xff08;IOC装配、AOP增强、常用注解&#xff09;5、Spring Boot/Spring Cloud1&#xff09;SpringBoot部分2&#xff09;SpringCl…

MySQL 视图(详解) navicat如何创建视图

文章目录MySQL 视图&#xff08;详解一&#xff0c;视图概念使用视图的原因二&#xff0c;创建视图&#xff08;1&#xff09;基本语法&#xff08;2&#xff09;创建基于单表的视图【实例 1】【实例 2】&#xff08;3&#xff09;创建基于多表的视图【实例 3】&#xff08;4&a…

使用set集合去除重复元素@EqualsAndHashCode注解

如何使用set集合去重 ​ 我们都知道&#xff0c;set集合是无序的&#xff0c;这样也导致set集合里面的元素是不能重复的&#xff0c;因为这一个特性&#xff0c;所以我们经常用set集合进行去重操作&#xff0c;我们下面以一个简单的例子说明set集合是如何进行去重的。 创建去…

缺少构造方法:Cause java.sql.SQLDataException Unsupported conversion from LONG to java.sql.Timestamp

今天遇到了一个奇怪的错误&#xff0c;报错如下图所示&#xff1a; org.springframework.dao.DataIntegrityViolationException: Error attempting to get column question_id from result set. Cause: java.sql.SQLDataException: Unsupported conversion from LONG to java…

SpringBoot瘦身打包部署

一、前言 最近做的项目由于引入第三方库导致在运行mvn clean package 打jar时&#xff0c;编译出来的 Jar 包很大&#xff08;服务器多达500MB&#xff09;。 二、瘦身前的Jar包 SpringBoot编译出来的Jar包中&#xff0c;磁盘占用大的&#xff0c;是一些外部依赖库&#xff…

XShell直接拖拽文件到服务器,不使用Xftp等文件上传工具

很多情况下&#xff0c;我们使用 Xshell 工具时&#xff0c;如果遇到文件的上传和下载会不可避免的要用到另外一个工具 Xftp&#xff0c;但是频繁的使用 Xftp 会比较麻烦&#xff0c;那么有没有一种更加直接简单的方法呢&#xff1f; 当我们所需要上传的文件比较小的时候&…

System.getProperty()方法获取系统变量

今天在阅读JDBC的DriverManager类源码时&#xff0c;看到了这么一句代码&#xff1a; System.getProperty(“jdbc.drivers”)&#xff1b;getProperty()这个方法是获取指定键指示的系统属性的&#xff0c;也就是说上面的代码获取的是jdbc.drivers这个属性。我写了个测试测试输…

局部变量为什么必须赋值才可以使用

在java内存模型中规定&#xff0c;一个新的变量只能在主存中初始化&#xff0c;不允许在工作内存中直接使用一个未被初始化的变量。 工作内存可以理解为局部变量定义的内存区域&#xff0c;也就是线程的工作内存。所谓局部变量就是线程私有的不共享的空间。 类加载准备阶段 类变…

Java 赋值 “=” 讲解

前言 我们从接触java第一天&#xff0c;就是到 是赋值的意思&#xff0c;把等号右边结果的值&#xff0c;赋给等号左边的变量&#xff0c;那具体是怎样赋值呢&#xff1f;你有了解过吗&#xff1f; 1.0版本 大家都知道&#xff0c;java中有 8大基本类型&#xff0c;对于基本…

Linux 系统管理命令:时间、进程、网络、磁盘、关机重启等 top命令用法详解

文章目录系统管理常用命令1. 日期1.1 查看日历: cal1.2 查看/设置时间: date2. 进程2.1 查看进程信息: ps2.2 动态显示进程信息: top2.3 终止进程: kill2.4 服务的管理: service3. 网络3.1 网卡信息查询与配置: ifconfig3.2 检测远程主机连通性: ping3.3 查看网络状态(监听端口…

Java8中计算时间的四种方式及区别Period、Duration、ChronoUnit、Until 时间区间Duration的简单使用

一.简述 在Java8中&#xff0c;我们可以使用以下类来计算日期时间差异&#xff1a; 1.Period 2.Duration 3.ChronoUnit二.Period类 Period类计算只有年、月、日 计算的是LocalDate两个时间间隔的年月日 public static void main(String[] args) {LocalDate startTime Loc…

[JAVA基础] 成员变量和局部变量(一看就懂的总结归纳篇)

引言 成员变量和局部变量在每种编程语言中都有涉及&#xff0c;如果之前了解过其他语言的成员变量或者局部变量&#xff0c;那么在学习java中的成员变量和局部变量时可以看看有那些联系和不同&#xff0c;这一块的东西也不能说难&#xff0c;如果第一次接触可能会感觉有点乱&a…

【Java多线程】内存模型JMM—主内存与工作内存分析

文章目录JAVA内存模型JVM主内存与工作内存描述JVM内存间交互规则JVM先行发生原则内存交互基本操作的 3 个特性原子性(Atomicity)可见性(Visibility)有序性(Ordering)上述内存模型与Java多线程之间的问题JAVA内存模型 共享变量&#xff1a;如果一个变量在多个线程的工作内存中都…

SpringBoot配置MyBatis的sql执行超时时间(mysql)

当某些sql因为不知名原因堵塞时&#xff0c;为了不影响后台服务运行&#xff0c;想要给sql增加执行时间限制&#xff0c;超时后就抛异常&#xff0c;保证后台线程不会因为sql堵塞而堵塞。 方法一 yml全局配置&#xff1a;单数据源可以&#xff0c;多数据源时会失效 方法二 j…

HTTP协议中的302,303状态码

之前也只知道302,303是请求重定向,但是当被问到302,303的具体区别是什么的时候我有点迷,现在就为了加强记忆,来了解下具体情况: 302是http1.0的内容&#xff0c;303是http1.1的内容。301和302本来在规范中是不允许重定向时改变请求方法的&#xff08;将POST改为GET&#xff09…

Spring自带工具类(断言、ObjectUtils、FileCopyUtils、ResourceUtils、StreamUtils、ReflectionUtils、AopUtils、AopCont)

文章目录断言对象、数组、集合文件、资源、IO 流反射、AOP断言 断言是一个逻辑判断&#xff0c;用于检查不应该发生的情况Assert 关键字在 JDK1.4 中引入&#xff0c;可通过 JVM 参数-enableassertions开启SpringBoot 中提供了 Assert 断言工具类&#xff0c;通常用于数据合法…

Arrays.asList踩坑——引发的Exception in thread “main“ java.lang.UnsupportedOperationException

Exception in thread “main” java.lang.UnsupportedOperationException 如果你尝试修改Arrays.asList方法生产的List&#xff0c;那么就会报这个错误 public static void main(String[] args) {Integer[] arr new Integer[]{7,8,9};List<Integer> list Arrays.asLi…

GIS算法:JAVA拓扑套件JTS

常用可以用于GIS数据处理和空间计算的java包有geotool和jts。 相对来说&#xff0c;geotool功能更全面&#xff0c;还可以用于数据转换、瓦片地图发布、栅格影像分析等&#xff0c;jts只能进行基本的数据处理和空间计算。 但大多数情况下jts就完全够用了。 geotool的官网&am…

Java本地远程服务器debug调试详解

日常我们debug是经常用的&#xff0c;但是本地还好说&#xff0c;远程debug就有点难度&#xff0c;而且有时候必须要在预演&#xff0c;测试环境的服务器去debug&#xff0c;举个例子&#xff0c;需要https&#xff0c;公网&#xff0c;域名之类的&#xff0c;测试服务器这些有…

Linux “ll“ 命令详解

“ls -l” “ls -al” ll 用来查询当前目录下文件及目录的详情 1.第一位文件类型 普通文件 &#xff0c; d 目录文件&#xff0c;I 链接文件&#xff0c;p 管理文件&#xff0c; b 块设备文件&#xff0c; c 字符设备文件&#xff0c; s 套接字文件 2.文件属性 第一部分表示文…