何谓线程安全?
在《java 并发编程实践》中对线程安全的定义如下:
当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。
很多时候,在单线程环境下很稳定的程序,到了多线程环境下,往往就变得不安全了。
1. 完全由线程安全的类构成的程序不一定就是线程安全的。
如下代码所示:
Vector v;
if(!v.contains(o)){
v.add(o);
}
虽然Vector是一个线程安全的类,但是对于由Vector线程安全的方法组成上面的逻辑,显然不是线程安全的
当然,由线程不安全的类构成的程序,也不一定不是线程安全的。
2. 多线程环境下,直觉有时候不准。
当变量的读入写出被不同的线程共享时,必须使用同步。若不使用同步将有可能发生与直觉大相径庭的错误。public class TestVisiable {
static int x=0,y=0;
static int b=0,a=0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable(){
public void run(){
a=1;
x=b;
}
});
Thread t2 = new Thread(new Runnable(){
public void run(){
b=1;
y=a;
}
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(x+" "+y);
}
}
由于没有同步,程序运行的可能结果为(1,0) (0,1),(1,1),然而,还有可能为(0,0),不可思议吧,这是由于为了加快并发执行,JVM内部采用了重排序的机制导致。
3. 简单类型中的特殊类型
在java中,java存储模型要求java变量的获取和存储都是原子操作,但是有两种类型的变量是比较特殊的
没有申明为volitale 的long和double变量。
原因在于,在java中long和double变量为64位,jvm允许将64位的读写操作划分为两个32位的操作。当多个线程同时读写非volatile的long或者double类型数据时,将有可能得到数据是一个数的高32位和另一个数的低32位组成的数。这样的结果显然是完全不对的。因此对应long或double类型的数据用于多线程共享时,必须申明加上volatile或者进行同步。
没有申明为volitale 的long和double变量。
原因在于,在java中long和double变量为64位,jvm允许将64位的读写操作划分为两个32位的操作。当多个线程同时读写非volatile的long或者double类型数据时,将有可能得到数据是一个数的高32位和另一个数的低32位组成的数。这样的结果显然是完全不对的。因此对应long或double类型的数据用于多线程共享时,必须申明加上volatile或者进行同步。