在Web开发中,不可避免的是需要遇到并发操作的,并发操作就有可能会引发我们的多线程安全问题。比如说,我们多线程下访问同一个变量并且有一个线程做出修改那么就会使得我们另外的线程在不知情的情况下被修改自己的数据。
如果说当我们其中一个线程访问了成员变量Object后并且设置为null,那么其他线程访问就会出现空指针异常了。我接触线程安全问题的时候是在数据库的连接操作。刚刚学习Web开发,使用了JDBC操作,而且对于servlet还是很懵懂。在开发的时候我竟然将Connection设置为成员变量,然后有同学点醒了我,说这样会引发线程安全问题。所以,在学习Spring的时候,我也会问自己是否线程安全的问题。
1. 多线程下单例有成员变量
1.1. 测试
首先这里有一个类User
:
public class User {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public long thisTime() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().toString()+"age:"+age);
age++;
return System.currentTimeMillis();
}
}
User
中有一个方法thisTime
这个方法访问了age并作出了+1操作的修改。
然后我们还有一个类MulThread
是用于访问User
的方法thisTime
:
public class MulThread extends Thread {
@Resource(name = "user")
private User user;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().toString()+user.thisTime());
}
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
现在我们可以开启线程用于测试模拟线程下,方法thisTime
的访问情况,这里我们的具体实现如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestMulThread {
@Resource
private MulThread th1;
@Resource
private MulThread th2;
@Test
public void test01() {
System.out.println(""+th1+th1.getUser()+"
"+th2+th2.getUser());
th1.start();
th2.start();
//等待线程结束
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们接下来在Spring配置Bean:
<bean name="user" class="cn.gcc.domain.User" scope="singleton">
<property name="age" value="18" />
</bean>
<bean class="cn.gcc.test.MulThread" scope="prototype"/>
1.2. 结果
Thread[Thread-2,5,main]cn.gcc.domain.User@cad498c
Thread[Thread-3,5,main]cn.gcc.domain.User@cad498c
Thread[Thread-2,5,main]age:18
Thread[Thread-3,5,main]age:18
Thread[Thread-2,5,main]1510027754876
Thread[Thread-3,5,main]1510027754876
Thread[Thread-3,5,main]age:20
Thread[Thread-2,5,main]age:20
.......
看到上面的运行结果我们可以看到19直接被跳过了,因为age++被同时执行了两次。因此,结论就是如果是单例模式下如果方法访问了同一个成员变量那么就会引起线程不安全的问题。
1.3. 修改
我们现在如果想用单例但是又要线程安全的话该怎么办?修改如下:
我们可以加锁synchronized
我们只需要修改User
的代码
public synchronized long thisTime() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().toString()+"age:"+age);
age++;
return System.currentTimeMillis();
}
Thread[Thread-2,5,main]cn.gcc.domain.User@cad498c
Thread[Thread-3,5,main]cn.gcc.domain.User@cad498c
Thread[Thread-3,5,main]age:18
Thread[Thread-3,5,main]1510028011839
Thread[Thread-2,5,main]age:19
Thread[Thread-2,5,main]1510028011939
Thread[Thread-3,5,main]age:20
....
上面的运行结果就不会有执行两次age++的情况了,因为我们对User
对象加锁操作,多个线程对同一个对象不能同时访问。
当然除了上面的方法也可以修改我们的Spring配置信息,<bean name="user" class="cn.gcc.domain.User" scope="prototype">
设置成多例就不会有这种问题了
2. 多线程下SpringMVC的Controller
我们在使用SpringMVC的时候默认Controller是单例的,因此,如果在设计Bean的时候是有成员变量的请参考上面所讲的 。
还有一种就是我们方法中的参数引用的是Bean,那么这个引用是不是在多线程下是同一地址呢?
现在我们写一个controller
@Controller
public class RegisterController {
@RequestMapping(value = "/reg.action", method = RequestMethod.GET)
public String reg(){
return "reg.jsp";
}
@RequestMapping(value = "/reg.action", method = RequestMethod.POST)
public String reg2(User user){
System.out.println(user);
return "reg.jsp";
}
}
这个controller中在填入表单之后的提交过程中,form表单的各个元素SpringMVC都会帮我们封装,现在我们使用post请求去访问/reg.action
然后看看结果是怎么样的
当我们使用了POST请求之后返回了reg.jsp的页面,控制台打印的信息是cn.gcc.domain.User@49cd75c3
我们再次访问后的打印信息是cn.gcc.domain.User@555b1f19
,因此,结论就是SpringMVC帮我们封装后的方法引用中是不会出现线程问题的
这里SpringMVC的封装的机制我猜测是根据setter方法来封装的,而且每次封装都会通过反射机制重新新建一个对象并且注入表单的值
下面是一些关于线程安全的链接
http://blog.csdn.net/xiao__gui/article/details/8188833
http://www.cnblogs.com/doit8791/p/4093808.html
https://zhuanlan.zhihu.com/p/29587112
http://blog.csdn.net/a236209186/article/details/61460211