Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方。
每一个request过来,系统都会调用原有的instance去处理,这样导致有俩个结果:
一是不用每次创建Controller
二是减少了对象创建和垃圾的收集的时间
当多用户同时请求一个服务的时候,容器会给每一个请求分配一个线程,并发执行该业务逻辑,如果逻辑中有处理该单列的成员属性时候,必须考虑线程同步。
考虑如下代码:
@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<Integer,User> usersCache = new HashMap<String,User>();
public void add(User user){
usersCache.put(user.getId(),user);
}
public void get(int id){
usersCache.get(id);
}
}
此段代码,usersCache对象就是线程不安全的。因为它是静态的全局共享对象。如果有多个线程同时调用add方法,可能会发生用户对象被覆盖的情况,也就是id对应对象不一致,这是多线程编程中最常发生的事情。
Spring 框架维护 Controller Dao Service(注入) 的(方法)线程安全。
SpringMvc和Servlet都是方法的线程安全,在类方法声明的公有和私有变量不是线程安全的。
如果用多个线程请求同一个Controller类中的同一个方法,线程不会阻塞。
Spring 框架使用了大量的ThreadLocal进制同步线数据 (空间换时间)
ThreadLocal 会为每一个线程提供一个独立的变量副本:
class SimpleThreadLocal {
private MapvalueMap = Collections.synchronizedMap(new HashMap());
public voidset(Object newValue) {
valueMap.put(Thread.currentThread(), newValue);//①键为线程对象,值为本线程的变量副本
}
publicObject get() {
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);// ②返回本线程对应的变量
if (o == null &&!valueMap.containsKey(currentThread)) {// ③如果在Map中不存在,放到Map
// 中保存起来。
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public voidremove() {
valueMap.remove(Thread.currentThread());
}
publicObject initialValue() {
return null;
}
}
线程安全问题都是由全局变量及静态变量引起的。