使用springboot单例模式与线程安全问题踩的坑

 更新时间:2021年08月04日 10:10:55   作者:会飞的基德  
这篇文章主要介绍了使用springboot单例模式与线程安全问题踩的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

springboot单例模式与线程安全问题踩的坑

最近有客户反映,使用公司产品时,偶尔会存在崩溃情况,自己测试无问题,然后去查日志,是报空指针。

于是顺藤摸瓜 往上找,好嘛,之前的开发使用了成员变量,感觉问题就是在这里了,因为众所周知,springboot 采用的是单例模式,所以,使用成员变量时一定要谨慎。

下面上一张该类的截图:

大家可能看到了,该类上面加上了@Scope("prototype") 注解,该注解的作用是将该类变成多例模式。讲道理因为变为了多例,应该不会有线程问题了。

我先说下我这边的一个代码环境,上面大家看到的BaseController这个类里面有个init方法,会在继承它的类的所有方法前执行。

使用的是@ModelAttribute注解,这个注解的意思是,在该controller的所有方法前执行,意在初始化,我猜测之前的同事应该是为了获取相同的一些参数,抽调出来做一个父类,随着迭代,别的同事为了方便,拿来就用,导致很多controller继承了该类。

@Scope("prototype")注解:

大家设想一下,若父类加了@Scope("prototype")注解,子类controller并没有加该注解,会怎样呢?该注解是否还有意义?再比如,我在某service上加上@Scope("prototype")注解,但调用的controller没有加@Scope("prototype")注解,那么会出现什么样的结果呢?大家可以去测试一下,测试方法也很简单,就是在对应的父类或service的无参构造方法里打印该类的地址。

下面说下我的测试结果:

先说父类上加了@Scope("prototype")注解,子类上没有加这种情况。结果是,同一子类继承的为同一父类,不同子类继承为不同父类。理解一下,很简单,因为springboot为单例模式,所以子类为单例,那么只有一个子类,父类肯定是一样的。所以,不同线程过来使用的为同一变量,就会有问题。

同理:

在service上标注@Scope("prototype")注解,那在同一个controller里,该service还是同一个,也就是说还是单例的,在不同的controller里 是不同的。测试方法同上。

现在说下解决方法:

1、是在继承该controller的子类上都加上@Scope("prototype")注解。这样做的好处是简单。坏处也同样明显,因为是多例的,那么就会产生大量的实体类,占用大量内存,若是回收不及时,有可能会出现内存溢出。

2、是将变量私有化,比如使用线程变量,对变量加锁等,技术上会复杂一些,而且调试不太好调试。说不定那些地方就会出现问题,毕竟是老代码。

3、将该类转换为拦截器,将变量放入request里,用的时候取出来。

SpringMVC 或 SpringBoot 默认是单例模式(Singleton)

多个请求是访问的同一个方法,是如何实现线程安全的?

SpringMVC Controller默认情况下是Singleton(单例)的,当request过来,不用每次创建Controller,会用原来的instance去处理。那么当多个线程调用它的时候,会不会发生线程不安全呢?

1、先说明下 Controller默认情况 单例的问题:

使用Spring MVC有一段时间了,之前一直使用Struts2,在struts2中action都是原型(prototype)的, 说是因为线程安全问题,对于Spring MVC中bean默认都是(singleton)单例的,那么用@Controller注解标签注入的Controller类是单例实现的?

测试结果发现spring3中的controller默认是单例的,若是某个controller中有一个私有的变量i,所有请求到同一个controller时,使用的i变量是共用的,即若是某个请求中修改了这个变量a,则,在别的请求中能够读到这个修改的内容。 若是在@Controller之前增加@Scope(“prototype”),就可以改变单例模式为多例模式

以下是测试步骤,代码与结果.

1. 如果是单例类型类的,那么在Controller类中的类变量应该是共享的,如果不共享,就说明Controller类不是单例。

以下是测试代码:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ExampleAction {
    private int singletonInt=1;
     @RequestMapping(value = "/test")
     @ResponseBody
     public String singleton(HttpServletRequest request,
             HttpServletResponse response) throws Exception {
         String data=request.getParameter("data");
         if(data!=null&&data.length()>0){
             try{
              int paramInt= Integer.parseInt(data);
             singletonInt = singletonInt + paramInt;
             }
             catch(Exception ex){
                 singletonInt+=10;
             }
         }else{
             singletonInt+=1000;
         }
         return String.valueOf(singletonInt);
    }
}

分别三次请求: http://localhost:8080/example/test.do?data=15

得到的返回结果如下。

第一次: singletonInt=15

第二次: singletonInt=30

第三次: singletonInt=45

从以上结果可以得知,singletonInt的状态是共享的,因此Controller是单例的。

2、对别Struts与springmvc对比

Struts2:默认prototype,Struts2 是基于类的,处于线程安全的考虑,采用了prototype模式,也就是说每次请求都会新建一个类来处理,自然就没有线程安全问题了,每次请求的类和数据都是单独的。

Springmvc:默认singleton 单例模式,Springmvc 是基于方法的,同一个url的请求是同一个实例处理的。每次请求都会把请求参数传递到同一个方法中,此时如果类里面有成员变量,那么这个变量就不是线程安全的了(例如上面的例子 private int singletonInt=1; 这个变量如果想线程安全则可以用ThreadLocal)。

在类中没有成员变量的前提下则是线程安全的。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Spring框架AOP面向切面编程原理全面分析

    Spring框架AOP面向切面编程原理全面分析

    这篇文章主要介绍了Spring框架AOP面向切面编程的全面分析,文中附含详细的示例代码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-09-09
  • MyEclipse配置JDK的全过程

    MyEclipse配置JDK的全过程

    这篇文章主要介绍了MyEclipse配置JDK的全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • springboot+vue实现验证码功能

    springboot+vue实现验证码功能

    这篇文章主要为大家详细介绍了springboot+vue实现验证码功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • springboot中实现上传文件的功能简单示例

    springboot中实现上传文件的功能简单示例

    这篇文章主要给大家介绍了关于springboot中实现上传文件功能的相关资料,在Spring Boot中实现文件上传下载功能相对简单,文中给出了代码示例,需要的朋友可以参考下
    2023-09-09
  • Java8新特性之新日期时间库的使用教程

    Java8新特性之新日期时间库的使用教程

    这篇文章主要给大家介绍了关于Java8新特性之新日期时间库使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Java实现简单的抽牌游戏

    Java实现简单的抽牌游戏

    这篇文章主要为大家详细介绍了Java实现简单的抽牌游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • java读取文件内容为string字符串的方法

    java读取文件内容为string字符串的方法

    今天小编就为大家分享一篇java读取文件内容为string字符串的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • java之assert关键字用法案例详解

    java之assert关键字用法案例详解

    这篇文章主要介绍了java之assert关键字用法案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • mybatisplus @Select注解中拼写动态sql异常问题的解决

    mybatisplus @Select注解中拼写动态sql异常问题的解决

    这篇文章主要介绍了mybatisplus @Select注解中拼写动态sql异常问题的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 详解Spring AOP的实现方式

    详解Spring AOP的实现方式

    AOP是一种思想,是对某一类事情的集中处理,切面就是指某一类特定的问题,所以AOP可以理解为面向特定方法编程,这篇文章主要介绍了Spring AOP的实现方式,需要的朋友可以参考下
    2024-02-02

最新评论