浅谈springboot如何保证多线程安全
⽬录
如何保证多线程安全
1.springboot在多线程并发访问下是怎么做的
3.⼩结⼀下
session怎么记忆单例模式与线程安全问题踩的坑
下⾯上⼀张该类的截图
我先说下我这边的⼀个代码环境
下⾯说下我的测试结果
现在说下解决⽅法
如何保证多线程安全
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<>();但是把这种使⽤的对象放进⽅法中初始化(即进⼊JVM栈中更好)。
当多个请求对controller进⾏请求时,它的instance的单例模式是线程不安全的,因此我们如果要保证完全的线程安全,需要对于每次请求都创建⼀个新的controller实例,在spring中使⽤@RequestScope注解定义它的作⽤域为requst,即⼀次请求即为⼀个实例,这样就可以保证controller层⾯上的线程安全。但是这样做会有⼀个很⼤的缺点,就是这种⽅式当并发很⼤时,创建bean的新实例就⽐重⽤原有的controller实例要慢许多。
因此还有折中的办法,就是将@RequestScope设置为session级别的作⽤域,这样每当⼀次会话,spring就会创建⼀个controller实例,⽽不需要每次请求都去创建⼀次实例,⼤⼤提⾼了访问的速度,虽然这样⽆法保证绝对的线程安全,但是在⼤部分的业务逻辑上都有效的防⽌了线程安全的问题。
此外,spring的作⽤域还有singleton(单例,也是spring默认的作⽤域级别,即永远使⽤同⼀个实例)、prototype(原型)、globalSession(全局)
3.⼩结⼀下
Spring本⾝并没有解决并发访问的问题。如果bean的范围不是线程安全的(例如在controller上⾯的成员变量或者静态变量就是线程不安全的),但其⽅法包含⼀些您总是希望安全运⾏的关键代码或者使⽤了静态字段需要对其进⾏并发修改,请在该⽅法上使⽤synchronized关键字。或者使⽤⼀些有提供线程安全的集合进⾏相应的多线程操作。
单例模式与线程安全问题踩的坑
最近有客户反映,使⽤公司产品时,偶尔会存在崩溃情况,⾃⼰测试⽆问题,然后去查⽇志,是报空指针。于是顺藤摸⽠往上,好嘛,之前的开发使⽤了成员变量,感觉问题就是在这⾥了,因为众所周知,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⾥,⽤的时候取出来。
以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论