在今天整理往前所学的知识中,看到了关于HashMap是线程不安全的,在之前的实际项目开发中自己在写一个消息通知的接口功大家调用的时候,因为一个消息通知的模板中需要替换的参数长度和数量不定,并且相同的参数可能对应不同的内容,为了方便大家调用我的这个方法,我使用了HashMap作为参数,并把它写在了Service层中一个方法内 。使用的框架是Spring+SpringMvc+SpringBoot+Mybatis+Theamleaf,当时在使用的时候知道如果前端发来多个请求的话Spring框架是将每一个请求都开启一个线程,但在Spring框架中Controller层和Service层和Dao层都用了加@Autowired注解的方式将对应的类注入到Spring中,再由Spring框架通过反射代理的方式生产一个单例,那么这样的后果是Spring框架在多线程中是线程不安全的,因为多个线程共用一个实例,但是为什么我在一个方法内写了线程并不安全的HashMap却没有错误呢?
一、SpringMvc下的controller和Service和Dao的创建模式
SpringMvc在默认配置下创建controller和Service和Dao是singleton的(非线程安全的),这也是和Struts2的区别,Struts2中会为每一个请求都创建对应的实例,所以它是线程安全的。
Spring这样做的好处:一是我们不用每次创建Controller,二是减少对象的创建和垃圾回收的频率,但是在多线程调用的时候因为都使用的是同一个instance,所以这个instance是线程不安全的,出现数据错误或者其他意想不到的结果。
二、怎么更改Spring默认创建的模式
最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”
singleton表示该bean全局只有一个实例,Spring中bean的scope默认也是singleton.
prototype表示该bean在每次被注入的时候,都要重新创建一个实例,这种情况适用于有状态的Bean.
三、为什么我的HashMap是安全的,不是说Spring框架是线程不安全的嘛?
我们首先来看一段线程不安全的HashMap代码:
@RequestMapping("/user")
@Controller
Class UserController
{
@Resource
UserService userService;
@RequestMapping("/add")
public void testA(User user){
userService.add(user);
}
@RequestMapping("/get")
public void testA(int id){
userService.get(id);
}
}
@Service("userService")
Class UserService{
public static Map usersCache = new HashMap();
public void add(User user){
usersCache.put(user.getId(),user);
}
public void get(int id){
usersCache.get(id);
}
}
此段代码,usersCache对象就是线程不安全的。因为它是静态的全局共享对象。如果有多个线程同时调用add方法,可能会发生用户对象被覆盖的情况,也就是id对应对象不一致,这是多线程编程中最常发生的事情。
所以,可以使用 Collections 工具同步Map。
static Map
或者也可以使用ConCurrent并发包下的ConCurrentHashMap来解决这个问题。
那么为什么我的HashMap也是在Service层写的,但却是线程安全的呢?
因为我的HashMap并不是一个静态的全局共享对象,只是一个方法里面的,我们来看我的代码:
这是我对应的Service层的方法,在这里我调用了我封装的一个发送消息的封装SendMessageUtil类来将消息生成,
可以看得出这里的HashMap并不是静态全局变量,它是属于一个方法的局部变量,方法的调用是属于JVM中的虚拟机栈的,是线程私有的,所以不会产生线程不安全问题。
总而言之
在Spring框架中只要将某个属性设置为静态全局变量就会有线程安全的问题,只要写在某一个方法内就不会有线程安全问题。