1.springboot在多线程并发访问下是怎么做的

        我们在Controller中,一般都是@AutoWired注入一些Service,由于这些Service都交给了spring进行管理,因此他们是单例的,对于在Controller中调用他们的方法,由于方法在JVM中属于栈操作,所以对于每一个线程来说,栈都是独立的,因此是线程安全的。

        而由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:一是我们不用每次创建Controller,二是减少了对象创建和垃圾收集的时间;由于只有一个Controllerinstance,当多个线程调用它的时候,它里面的instance变量就不是线程安全的了,会发生窜数据的问题。

        如果我们定义了一个全局的对象,如 private Company company = new Company(); 而在@RequestMapping请求的方法中去用到了它, 这里就存在并发线程安全的问题。对于每个请求,这个company是互通的。

总结以上问题:不要在Controller里出现类的实例。即便加了线程安全操作,也会出现性能问题。当然无论是Controller还是Service,如果你一定要使用对象的属性,如private Company company = new Company();可以加上ThreadLocal的引用,如private ThreadLocal<Company> tc = new ThreadLocal<>();但是把这种使用的对象放进方法中初始化(即进入JVM栈中更好)。

2.controller在多线程下如何尽可能保证线程安全,如何取舍

方法之一:

        当多个请求对controller进行请求时,它的instance的单例模式是线程不安全的,因此我们如果要保证完全的线程安全,需要对于每次请求都创建一个新的controller实例,在spring中使用@RequestScope注解定义它的作用域为requst,即一次请求即为一个实例,这样就可以保证controller层面上的线程安全。但是这样做会有一个很大的缺点,就是这种方式当并发很大时,创建bean的新实例就比重用原有的controller实例要慢许多。

        因此还有折中的办法,就是将@RequestScope设置为session级别的作用域,这样每当一次会话,spring就会创建一个controller实例,而不需要每次请求都去创建一次实例,大大提高了访问的速度,虽然这样无法保证绝对的线程安全,但是在大部分的业务逻辑上都有效的防止了线程安全的问题。

        此外,spring的作用域还有singleton(单例,也是spring默认的作用域级别,即永远使用同一个实例)、prototype(原型)、globalSession(全局)。

3.小结一下

        Spring本身并没有解决并发访问的问题。如果bean的范围不是线程安全的(例如定义在controller中的成员变量或者静态变量就是线程不安全的),但其方法包含一些您总是希望安全运行的关键代码或者使用了静态字段需要对其进行并发修改,请在该方法上使用synchronized关键字。或者使用一些有提供线程安全的集合进行相应的多线程操作。

线程安全的集合类比如:

        


List<String> list = new CopyOnWriteArrayList<>();
 
Map<String, String> map = new ConcurrentHashMap<>();
 
Set<String> set = new CopyOnWriteArraySet<>();


spring boot的并发问题: