为什么会有上面的结果,因为java的线程允许线程在自己的内存区保存变量的副本,而线程运行时候是使用本地的变量副本操作的,副本是一个临时变量,最终某个时刻都会和主存保持同步,但是过程中使用临时备份那么程序的性能就会越好。而上面的问题正是因为两个线程里的拷贝和主存不一致。而让线程的临时备份保持和主存的一致一般使用两个办法:
1.变量使用volatile声明
2.被访问的变量处于同步方法或者同步块中
上面的解释道出了java里volatile关键字的作用:让线程里的临时拷贝的内存和主存保持一致。
原子操作只有在对基本类型进行读取或赋值时候才被认为是线程安全的,不过正如EvenGenerator中所见,原子操作也很容易访问到对象处在不稳定时候的数值,这种不稳定让使用volatile变量实现同步变的不是很可靠,就算用它做出了安全的线程同步程序员付出的代价也是很大。最安全的也最简单的线程安全做法可以使用下面的方法:
①如果要对类中的某个方法进行同步控制,最好同步所有的方法,假如有被漏掉的方法,结果就很难被我们把控,我们的程序也会带有线程随机调度的特点了。
②假如我们要真的去掉某个方法前的同步标记(这种情况往往是性能的考虑,线程同步总会消耗系统宝贵的计算资源),要非常小心,除非万不得已,要不千万别这么做。
线程同步会消耗我们宝贵的计算资源。我们写软件总希望它是很快很快再快些,这也是非专业用户觉得你软件做的好的最重要指标之一。但是为了保证线程安全我们在很多方法前加上synchronized关键字导致方法变慢,这个代价虽然我们不得不去承受,但是能不能有变通的方案呢?
当然有,一个方法里真正需要线程的可能只有一部分,而多个线程也只是需要访问这一部分代码,而不是访问整个方法,那么我们就可以把这段代码分离出来,java语言给这样的代码起了一个名字:临界区(critical section)。同步块的模式如下:
synchronied(syncObject){
//This code can be accessed
//by only one thread at a time
}同步块使用synchronized关键字定义,synchronized关键字会告诉java语言获得syncObject对象的锁,而对象的锁被用来对花括号内的代码进行同步控制。哈哈,只是同步一部分代码而不是方法里所有代码,这样的做法效率一定会有显著的提高。
下面我将写一段程序,这段程序里会比较两种同步控制。这段代码还会演示如何把一个非保护类型的类在其他类的保护和控制之下应用到多线程的环境里:
package cn.com.sxia;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* 该类线程不安全
* @author Administrator
*/
class Pair{
private int x,y;
public Pair(int x,int y){
this.x = x;
this.y = y;
}
public Pair(){
this(0,0);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void incrementX(){
x++;
}
public void incrementY(){
y++;
}
@Override
public String toString() {
return "Pair [x=" + x + ", y=" + y + "]";
}
public void checkState(){
if (x!=y){
throw new PairValuesNotEqualException();
}
}
//异常内部类
public class PairValuesNotEqualException extends RuntimeException{
public PairValuesNotEqualException(){
super("Pair类的数值不相等:" + Pair.this);
}
}
}