使用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)。

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

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

(0)

相关推荐

  • Spring如何解决单例bean线程不安全的问题

    首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么啦? 因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题 因为Spring中的Bean默认是单例的,所以在定义成员变量时也有可能会发生线程安全问题.下面我们就来研究下如何解决Spring中单例Bean的线程安全问题 @RestController //@Scope("prototype") public class BeanController { private int content=0; //基

  • java多线程之线程安全的单例模式

    概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能有一个Printer

  • SpringBoot 定时任务遇到的坑

    前言 springboot已经支持了定时任务Schedule模块,一般情况已经完全能够满足我们的实际需求.今天就记录一下我使用 schedule 时候踩的坑吧. 想要使用定时,我们首先要开启支持,其实就是在启动类上面加个注解就 Ok. @SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(A

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

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

  • spring boot中多线程开发的注意事项总结

    前言 Springt通过任务执行器(TaskExecutor)来实现多线程和并发编程.使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.而实际开发中任务一般是非阻碍的,即异步的,所以我们要在配置类中通过@EnableAsync 开启对异步任务的支持,并通过实际执行Bean的方法中使用@Async注解来声明其是一个异步任务. 基于springboot的多线程程序开发过程中,由于本身也需要注入spring容器进行管理,才能发挥springboot的优势.

  • 浅谈Spring中单例Bean是线程安全的吗

    Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究. Spring 的 bean 作用域(scope)类型 1.singleton:单例,默认作用域. 2.prototype:原型,每次创建一个新对象. 3.request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下. 4.session:会话,同一个会

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

    springboot单例模式与线程安全问题踩的坑 最近有客户反映,使用公司产品时,偶尔会存在崩溃情况,自己测试无问题,然后去查日志,是报空指针. 于是顺藤摸瓜 往上找,好嘛,之前的开发使用了成员变量,感觉问题就是在这里了,因为众所周知,springboot 采用的是单例模式,所以,使用成员变量时一定要谨慎. 下面上一张该类的截图: 大家可能看到了,该类上面加上了@Scope("prototype") 注解,该注解的作用是将该类变成多例模式.讲道理因为变为了多例,应该不会有线程问题了.

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

    目录 一. 使用多线程需要考虑的因素 二. 单例模式 1. 饿汉模式 2. 懒汉模式 3. 懒汉模式(使用synchronized改进) 4. 懒汉模式(使用双重校验锁改进) 三. volatile的原理 四. volatile的扩展问题(了解) 一. 使用多线程需要考虑的因素 提高效率:使用多线程就是为了充分利用CPU资源,提高任务的效率线程安全:使用多线程最基本的就是保障线程安全问题 所以我们在设计多线程代码的时候就必须在满足线程安全的前提下尽可能的提高任务执行的效故:加锁细粒度化:加锁的代

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

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

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

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

  • Spring Bean的线程安全问题

    目录 @Controller.@Service是不是线程安全的? ThreadLocal vs 线程同步机制 线程同步机制 ThreadLocal 总结 Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究. Spring的bean作用域(scope)类型: singleton prototype request session global-sessi

  • Java线程安全问题小结_动力节点Java学院整理

    浅谈java内存模型 不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的.其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改.总结java的内存模型,要解决两个主要的问题:可见性和有序性.我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的.JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要清楚在jvm内存模型的基础上,如果解决多线程的可见性和有序性. 那么,何谓

  • 老生常谈C++的单例模式与线程安全单例模式(懒汉/饿汉)

    1 教科书里的单例模式 我们都很清楚一个简单的单例模式该怎样去实现:构造函数声明为private或protect防止被外部函数实例化,内部保存一个private static的类指针保存唯一的实例,实例的动作由一个public的类方法代劳,该方法也返回单例类唯一的实例. 上代码: class singleton { protected: singleton(){} private: static singleton* p; public: static singleton* instance()

  • 从C++单例模式到线程安全详解

    先看一个最简单的教科书式单例模式: class CSingleton { public: static CSingleton* getInstance() { if (NULL == ps) {//tag1 ps = new CSingleton; } return ps; } private: CSingleton(){} CSingleton & operator=(const CSingleton &s); static CSingleton* ps; }; CSingleton*

  • 基于java线程安全问题及原理性分析

    1.什么是线程安全问题? 从某个线程开始访问到访问结束的整个过程,如果有一个访问对象被其他线程修改,那么对于当前线程而言就发生了线程安全问题:如果在整个访问过程中,无一对象被其他线程修改,就是线程安全的. 2.线程安全问题产生的根本原因 首先是多线程环境,即同时存在有多个操作者,单线程环境不存在线程安全问题.在单线程环境下,任何操作包括修改操作都是操作者自己发出的,操作者发出操作时不仅有明确的目的,而且意识到操作的影响. 多个操作者(线程)必须操作同一个对象,只有多个操作者同时操作一个对象,行为

随机推荐