Spring集成的项目中,通过注解方式注入Bean的使用无处不在。在一次项目开发中,由voliate关键字思考到的Spring中单例Bean的线程安全问题,在此记录采坑总结。
Spring的单例Bean(@Autowired等注解的Bean)默认是单例的,Spring并没有对Bean有线程安全的控制策略,并发安全问题由开发决定。然而,线程安全与否取决于是否有共享的资源竞争关系存在,Spring中单例Bean是否是线程安全取决于Bean是有状态的Bean还是无状态的Bean。
何为有状态Bean跟无状态Bean?
- 有状态的Bean:有数据存储功能,eg:Bean中有User对象用于存储用户信息;
- 无状态的Bean:无数据存储功能
下面是测试的Demo:
(1)MainUser.java:
@Data
@Builder
public class MainUser {
private String id;
private String sex;
private String userName;
}
(2)SysMainController .java
@RequestMapping("/main")
@Controller
public class SysMainController {
@Autowired
private SysMainService mainService;
@RequestMapping(value = "/test")
public void mainMethod() {
// thread-1
new Thread(() -> {
MainUser user = MainUser.builder()
.id("1")
.sex("男")
.userName("张三")
.build();
mainService.testBean(user);
}).start();
// thread-2
new Thread(() -> {
MainUser user = MainUser.builder()
.id("2")
.sex("女")
.userName("小芳")
.build();
mainService.testBean(user);
}).start();
}
}
(3)SysMainServiceImpl.java
@Service
public class SysMainServiceImpl implements SysMainService {
/** 基本类型变量亦可 */
private MainUser user; // 非定义为static的变量
@Override
public void testBean(MainUser user) {
this.user = user;
print();
}
public void print() {
try {
Thread.sleep(2000);
System.out.println("ThreadName: " + Thread.currentThread().getName() + ", user: " + user);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试多次的结果:
ThreadName: Thread-57, user: MainUser(id=2, sex=女, userName=小芳)
ThreadName: Thread-58, user: MainUser(id=2, sex=女, userName=小芳)
结论:
- 两个线程进入print()方法都会创建自己的私有VM,但是user对象是共享的,这点容易在开发中被忽略;
- 故在Spring的单例模式的Bean中不应该在全局层面上定义有数据存储功能的对象 / 基本类型变量 / static变量,会产生线程安全问题;若要这么做,可以通过声明Bean的 Scope=‘prototype’ 或者使用本地变量 ThreadLocal 进行定义
以上为笔者遇到的问题进行的思考实验,若有不正确的地方欢迎指出~~~