springboot如何保证多线程安全
1.springboot在多线程并发访问下是怎么做的?
我们在Controller下,⼀般都是@AutoWired⼀些Service,由于这些Service都交给了spring进⾏管理,因此他们单例的,对于在Controller中调⽤他们的⽅法,由于⽅法在JVM中属于栈操作,所以对于每⼀个线程来说,栈都是独⽴的,因此是线程安全的。 ⽽由于Controller本⾝是单例模式 (⾮线程安全的), 这意味着每个request过来,系统都会⽤原有的instance去处理,这样导致了两个结果:⼀是我们不⽤每次创建Controller,⼆是减少了对象创建和垃圾收集的时间;由于只有⼀个Controller的instance,当多个线程调⽤它的时候,它⾥⾯的instance变量就不是线程安全的了,会发⽣窜数据的问题。 如果我们定义了⼀个全局的实例,如 private Company company = new Company(); ⽽在@RequestMapping⽅法中去⽤到了他, 这⾥就存在并发线程安全的问题。对于所有的请求request,这个company对象是相通的。 当然我们也可以⽤这个特性来制作访问计数器 只需要定义⼀个private int cout = 0; 在每⼀次请求后cout++;
当然我并不推荐这么做,计数器最好⽤redis来操作。
总结以上问题,不要在Controller⾥出现类的实例。即便加了线程安全操作,也会出现性能问题。当然⽆论是Controller还是Service,如果你⼀定要使⽤对象的属性,如private Company company = new Company();可以加上ThreadLocal的引⽤,如private ThreadLocal<Company> tc = new ThreadLocal<>()
session怎么记忆;但是把这种使⽤的对象放进⽅法中初始化(即进⼊JVM栈中更好)。
当多个请求对controller进⾏请求时,它的instance的单例模式是线程不安全的,因此我们如果要保证完全的线程安全,需要对于每次请求都创建⼀个新的controller实例,在spring中使⽤@RequestScope注解定义它的作⽤域为requst,即⼀次请求即为⼀个实例,这样就可以保证controller层⾯上的线程安全。但是这样做会有⼀个很⼤的缺点,就是这种⽅式当并发很⼤时,创建bean的新实例就⽐重⽤原有的controller实例要慢许多。
因此还有折中的办法,就是将@RequestScope设置为session级别的作⽤域,这样每当⼀次会话,spring就会创建⼀个controller实例,⽽不需要每次请求都去创建⼀次实例,⼤⼤提⾼了访问的速度,虽然这样⽆法保证绝对的线程安全,但是在⼤部分的业务逻辑上都有效的防⽌了线程安全的问题。
此外,spring的作⽤域还有singleton(单例,也是spring默认的作⽤域级别,即永远使⽤同⼀个实例)、prototype(原型)、globalSession(全局)
3.总结
Spring本⾝并没有解决并发访问的问题。如果bean的范围不是线程安全的(例如在controller上⾯的成员
变量或者静态变量就是线程不安全的),但其⽅法包含⼀些您总是希望安全运⾏的关键代码或者使⽤了静态字段需要对其进⾏并发修改,请在该⽅法上使
⽤synchronized关键字。或者使⽤⼀些有提供线程安全的集合进⾏相应的多线程操作。