下面的Java内存模型下一些天然的发生关系,这些发生关系无须任何同步器,就已经存在,可以在编码中直接使用。如果有两个操作关系不在此列,并且无法从下列关系推导出来,它们就没有顺序保障,虚拟机可以对它们随意重排序。
1)程序次序规则:在一个线程内,按照程序代码顺序或控制流,书写在前在的操作先行发生于写在后面的操作。
2)管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调是同一个对象锁,而后面,指的是时间上的先后顺序。
3)volatitle变量规则:对一个volatitle变量的写操作先行发生于后面对这个变量的读操作。
4)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
5)线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。我们可以通过join()方法结束、isAlive()返回值检测线程的终止。
6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。可以通过interrupted()检测是否有中断发生。
7)对象终结规则:一个对象的初始化完成(构造方法执行结束)先行发生于它的finalize()方法的开始。
8)传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那么可以得出操作A先行发生于操作C的结论。
我们来看个例子:
private int value = 0;
public void setValue(int value){
this.value = vlaue;
}
public int getValue(){
return value;
}
private int value = 0; public void setValue(int value){ this.value = vlaue; } public int getValue(){ return value; } 假设存在线程A和线程B,线程A先(时间上的先后)调用setValue(1),然后线程B调用同一个对象的getValue(),会得到什么结果呢?我们依次分析下先行发生原则里的各项规则,
由于两个方法分别由线程A和线程B调用,不在一个线程中,所以程序次序规则在这里不适用。
由于没有同步块,自然没有lock与unlock,所以管程锁定规则在这里不适用。
由于变量value没有用volatitle修饰,所以volatitle变量规则在这里不适用。
后面的线程启动、终止、中断、对象终结都跟这没关系。
因为没有一个适用的先行发生规则,所以传递性也不适用。
所以我们可以判定尽管线程A在操作时间上先于线程B,但是无法确定线程B的返回结果,即这里操作不是线程安全的。
那么怎么修复这个问题呢,很简单,要么给seter/geter 方法加上synchronized关键字,这样就可以使用管程锁定规则,要么把变量value定义为volatitle类型,由于setter方法对value的修改不依赖value的原值,满足volatitle关键字的使用场景,这样就可以使用volatitle变量规则。