如果Java内存模型中所有有序性都靠volatile和synchronized来完成,那么编写代码会很繁琐,但日常Java开发中并没有感受到这一点,正是因为Java语言的“先行发生”原则。这个原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据。
先行发生是Java内存模型中定义的两项操作数之间的偏序关系,如果说操作A先行发生于操作B,就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。
下面是Java内存模型中一些“天然的”先行发生关系,这些先行发生关系无需任何同步协助器协作即可存在,可以直接在编码中使用。如果两个关系不在此列,而又无法通过这些关系推导出来,它们的顺序就无法保证,虚拟机可以对它们任意重排序。
- 程序次序规则 :一个线程内,按照程序代码的顺序,书写在前面的操作先行发生于(逻辑上)书写在后面的操作。
- 管程锁定规则 :一个unlock操作先行发生于后面对同一个锁的lock操作。后面指时间上的先后顺序。
- volatile变量规则 :对一个volatile变量的写操作先行发生于后面对这个变量的读操作。这里的后面指时间上的先后顺序。
- 线程启动规则 :Thread对象的start方法先行发生于此线程的每个动作;
- 线程终止规则 :线程中的所有操作都先行发生于对此线程的终止检测;
- 线程中断规则 :对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断时间的发生;
- 对象终结规则 :一个对象的初始化完成先行发生于它的finalize方法的开始;
- 传递性 :如果操作A先行发生于操作B,操作B先行发生于操作C,那么,操作A也就先行发生于操作C。
java无须任何手段即可保证上面的先行发生规则成立,下面那个例子看一下:
private int value = 0;
public void setValue(int value){
this.value = value;
}
public int getValue(){
return value;
}
假设线程A先(时间上的先后)执行setValue(1), 然后线程B调用同一对象的getValue(),那么线程B收到的返回值是什么?
依次分析一下先行发生原则中的各个原则:由于两个方法分别在不同的线程中被调用,程序次序原则不适用;没有同步块,自然不会发生lock和unlock操作,管程锁定原则不适用;value变量没有被volatile修饰,volatile变量原则不适用;后面的线程启动、中断、终止原则也毫无关系;没有一个适用的原则,传递性也不适用。所以说线程B得到的结果不确定是0还是1,换句话说,这里面的操作不是线程安全的。