在多线程程序中,如果多个线程同时对同一个对象进行读写,由于读写操作会在内存中先复制一份缓存数据,修改完缓存数据后再用缓存数据覆盖原来的数据,所以写操作不会马上影响到原始数据,这时候其他线程读取到的数据就是旧的数据(也成为“脏数据”),这个数据的读写就是线程不安全了。在数据库里面所谓的“脏读”也是类似的原理。
例子程序:
class UnSafeClass{
// 线程不安全的类
private int a = 0;
public void add(){
a+=1;
System.out.println("UnSafeClass: " + Thread.currentThread().getName() + ":a is " + a);
}
}
class SafeClass{
// 线程安全的类
private int a = 0;
public void add(){
synchronized (this) {
a++;
System.out.println("SafeClass: " + Thread.currentThread().getName() + ":a is " + a);
}
}
}
class TestUnSafeThread implements Runnable{
private static UnSafeClass unSafeClass = new UnSafeClass();
@Override
public void run(){
for(int i=0; i<10; i++) {
try {
unSafeClass.add();
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
class TestSafeThread implements Runnable{
private static SafeClass safeClass = new SafeClass();
@Override
public void run() {
for(int i=0;i<10;i++) {
try {
safeClass.add();
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
public class SafeThreadTest{
public static void main(String[] args){
System.out.println("SafeThreadTest-------------------");
Thread[] t1 = new Thread[3];
for(int i=0;i<t1.length;i++){
t1[i] = new Thread(new TestUnSafeThread());
t1[i].start();
}
Thread[] t2 = new Thread[3];
for(int i=0;i<t2.length;i++){
t2[i] = new Thread(new TestSafeThread());
t2[i].start();
}
}
}
程序输出结果
由结果可以看出来,线程不安全的类读写存在脏读的情况(不一定每次都会出现),而线程安全的类(即加锁)读取没有存在脏读的情况。
对象的无状态与有状态
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。
// 无状态对象
class StatelessClass {
public void method(){
//do something
....
}
}
// 有状态对象
class stateClass{
private int count = 0; // 对象的状态变量
public void method(){
count ++; // 对象的变量变化,在多线程的时候不安全。
}
}
利用无状态的技术有单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。 有状态的bean都使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
关于volatile
对于非volatile类型的long和double变量,jvm允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile类型的long变量是,如果对改变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。因此,即使不考虑失效数据问题(就是读取到了“脏数据”),在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明或者用锁保护起来。
当把变量声明为volatile类型后,编译器与运行的虚拟机都会注意到这个变量是共享的,因此不会讲改变量上的操作与其他内存操作一起重排序。在访问volatile变量是不会执行加锁操作,因此也就不会是执行线程阻塞,所以volatile变量是一个种比sychronized关键字更轻量级的同步机制。
volatile变量一般用于某个状态标记变量,volatile变量不能确保数据的原子性,只能确保可见性(内存可见性)