刚毕业的时候,总是搞不清什么是线程安全,只记得网上说,只要方法做到可重入,就是线程安全的,晕头晕脑,一头雾水。
今年读Java并发编程,对并发有了进一步的了解,结合我们项目的实际应用,谈一下我对线程安全的认识。
一、共享变量(share varibles)引起线程安全
如果没有共享,就不会有线程安全问题。比如,你写的就是一个简单的helloword程序,不会有线程安全问题。但如果你的程序是一个web项目,并且里面有一个全局适用的缓存对象,那么就会涉及到线程安全问题。
下面是一个静态方法,根据code_id,得到code_name的方法。
public class CodeHelper {
private static Map<Integer,String> CODE_DATA ;
public static void initCode(){
//load from db ..
}
public static String getCodeNameById(int codeId){
if(CODE_DATA==null){
initCode();
}
return CODE_DATA.get(codeId);
}
}
变量CODE_DATA就会涉及到线程安全问题。我们考虑,如果同时有两个线程调用getCodeNameById的方法,那么initCode方法就会被执行两边,而这却不是我们希望看到的。所以说,只要涉及到静态变量,我们就要小心了,静态变量,有可能存在线程安全问题。
二,单实例引起线程安全
我们知道,spring中的bean默认都是单实例(prototype=singleton)的,也就是说,spring里面的bean,其所有的成员变量都基本上是与静态变量是类似的。bean的任何方法,都必须代是可重入的,而不能在某个方法里面,设置bean的某个值,并且依赖其状态。比如,下面的代码就会出现线程问题。
public class JavaBean {
private String myname;
public void aservice(String paraName,int otherPara){
//在spring的默认配置下在这里设值,另一个方法使用是错误的
this.myname=paraName;
bservice();
}
private void bservice() {
String goodName= this.myname;
//other work
}
public String getMyname() {
return myname;
}
public void setMyname(String myname) {
this.myname = myname;
}
}
如果有两个线程A和B,同时进入aservice,A线程设置myname="AName",然后在执行到bservice线程阻塞,此时B线程进入,将myname="BName",然后进入bservice得出正确结果而退出。B线程执行后,myname="BName",A线程恢复,在bservice的方法执行时,使用的myname="BName",计算肯定会出错。但如果你每次使用JavaBean的时候,都是new一个,就不会存在线程安全的问题了。
虽然线程安全是一个不是很容易理解的话题,但上面两个例子却反映了两个最普遍的线程安全问题。遇到线程问题,最快的解决方法就是去掉变量共享。单线程的程序,永远不会出现线程安全问题。