先看这么一段代码:
@Service
public class AccountService {
private String message;
public void foo1() {
if (true) {
this.message = "a";
} else {
this.message = "b";
}
}
public void foo2() {
// 改动this.message的代码...
// ... ...
}
}
假设你打算在@Controller里这么调用AccountService :
accountService.foo1();
model.addAttribute(accountService.getMessage());
那么就有线程安全的危急了。
问题原因
在Spring中。bean的默认scope是singleton,也就是说容器中仅仅有一个bean的实例。而在Java Web环境中,webserver会为每个请求创建一个线程来处理它。这样一来。在@Controller中调用@Service bean的方法就会导致有多个线程在运行@Service方法。比如线程A在运行foo1()方法,线程B在运行foo2()方法。
那么问题来了,多个线程同一时候读写message成员变量。就可能让getMessage()方法返回错误的值
解决方法
1. 将@Service bean的scope改为 "request",即:
@Service
@Scope("request")
public class AccountService {
private String message;
这样Spring会为每个请求分别创建一个AccoutService对象,每个线程都有自己的message变量。就不会出错了。
但坏处是创建@Service bean的开销往往比較大,会导致程序性能下降。
2. 使用不可变对象(Immuable Object)封装message变量
定义例如以下类:
class MessageWrapper {
private String message;
public MessageWrapper(String msg) {
this.message = msg;
}
// 仅仅提供get方法
public String getMessage() {
return this.message;
}
}
AccountService的foo1()方法改动例如以下:
@Service
public class AccountService {
public MessageWrapper foo1() {
if (true) {
return new MessageWrapper("a");
} else {
return new MessageWrapper("b");
}
// ... ...
}
这样便能够完美避免线程安全问题,又不会带来过多的额外开销。