Bootstrap

springboot中关于@controller、@Service单例模式、多例模式和多线程安全的简单概述

说明:该演示内容是基于SpringBoot的2.1.8.RELEASE版本.这里先抛出结论,然后分别去举例演示。

1. springboot中@Controller、@Service模式是使用的单例,即@Scope("singleton"),如果要修改可以加@Scope("prototype")注解;

如单例模式:

package com.chs.nginxdemo.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
public class BankController {
    private int index;
    private static int staicIndex;
 
    @RequestMapping("/query")
    @ResponseBody
    public String queryBank() {
        System.out.println("普通属性:" + index++ + "..." + "静态属性:" + staicIndex++);
        return "ok";
    }
}

打印输出如下:

普通属性:0...静态属性:0
普通属性:1...静态属性:1
普通属性:2...静态属性:2
普通属性:3...静态属性:3
普通属性:4...静态属性:4

我们可以看出非静态成员变量是共用的,一直在递增;静态成员也在递增。

多例模式: 

package com.chs.nginxdemo.controller;
 
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
@Scope("prototype")
public class BankController {
    private int index;
    private static int staicIndex;
 
    @RequestMapping("/query")
    @ResponseBody
    public String queryBank() {
        System.out.println("普通属性:" + index++ + "..." + "静态属性:" + staicIndex++);
        return "ok";
    }
}

普通属性:0...静态属性:0
普通属性:0...静态属性:1
普通属性:0...静态属性:2
普通属性:0...静态属性:3
普通属性:0...静态属性:4

我们看到非静态成员变量每次是创建,都是0;静态成员变量每次递增;由此看出一般不要在单例模式下使用非静态成员变量。

2. 如果controller(单例)继承的基类是多例,那么controller也是单例,不会继承;

package com.chs.nginxdemo.controller;
 
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
 
@Controller
@Scope("prototype")
public class BaseController {
 
}
package com.chs.nginxdemo.controller;
 
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
 
@Controller
@Scope("prototype")
public class BaseController {
 
}

打印输出如下:

普通属性:0...静态属性:0
普通属性:1...静态属性:1
普通属性:2...静态属性:2
普通属性:3...静态属性:3
普通属性:4...静态属性:4

由此看出此基类多例,子类单例,仍然是单例,不会继承;

3.如果controller是多例, service 是单例。那么会service依然是单例,即不会创建多个service;

package com.chs.nginxdemo.controller;
 
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
 
@Controller
@Scope("prototype")
public class BaseController {
 
}



package com.chs.nginxdemo.controller;
 
import com.chs.nginxdemo.service.SocketClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
@Scope("prototype")
public class BankController extends BaseController {
    private int index;
    private static int staicIndex;
    @Autowired
    private SocketClient socketClient;
 
    @RequestMapping("/query")
    @ResponseBody
    public String queryBank() {
        String data = socketClient.doTrade();
        System.out.println("普通属性:" + index++ + "..." + "静态属性:" + staicIndex++ + "..." + "data:" + data);
        return "ok";
    }
}


package com.chs.nginxdemo.service;
 
import org.springframework.stereotype.Service;
 
@Service
public class SocketClient {
    private int index;
 
    public String doTrade() {
        return Integer.toString(index++);
    }
 
}

打印输出:

普通属性:0...静态属性:0...data:0
普通属性:0...静态属性:1...data:1
普通属性:0...静态属性:2...data:2
普通属性:0...静态属性:3...data:3

由此看出,单例service依然是单例。把service设成多例,代码如下 

package com.chs.nginxdemo.service;
 
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
 
@Service
@Scope("prototype")
public class SocketClient {
    private int index;
 
    public String doTrade() {
        return Integer.toString(index++);
    }
 
}

打印输出如下:

普通属性:0...静态属性:0...data:0
普通属性:0...静态属性:1...data:0
普通属性:0...静态属性:2...data:0
普通属性:0...静态属性:3...data:0
普通属性:0...静态属性:4...data:0

可以看出 data都是0,变成多例了。

综上所述,想比较简单的解决并发问题,需要controller和 service都设置成多例。

4.并发问题,关于并发问题,基于该场景先明确下什么情况下会发生并发问题呢。一般若每个线程中的静态变量、实例(对象)变量只有读操作、而无写操作,那么通常情况下这个全局变量是线程安全的;但是若有多个线程同时执行写操作,那么通常情况下需要考虑线程同步问题,否则就可能会出现线程安全问题。如:
1)常量始终是线程安全的,因为只存在读操作; 
2)局部变量(包括方法的参数变量和方法内变量)是线程安全的。因为每执行一个方法,都会在独立的空间(栈帧)创建局部变量,它不是共享的资源;
3)成员变量(实例变量和类变量)会受到多线程影响。对于成员变量的操作,可以使用ThreadLocal来保证线程安全;

4)类变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果;而对象变量则属对象私有,某一个对象将其值改变,不影响其他对象。

最后说明下关于变量的基本概念,在java语言里,根据定义变量位置的不同,可以将变量分成两大类:

  • 成员变量,存在与堆内存中和类一起创建。如实例变量(不以static修饰)、类变量(以static修饰)
  • 局部变量,存在与栈内存中,当方法执行完成后,让出内存让其它方法来使用内存。如形参(方法签名中定义的变量)、方法局部变量(在方法内部定义的变量)、代码块局部变量(在代码块内定义的变量)
;