45

安全并发之先行发生原则

 5 years ago
source link: http://www.cnblogs.com/yougewe/p/9734255.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

先行发生原则,可以帮你判定是否并发安全的,从而不必去猜测是否是线程安全了!

如果Java内存模型中所有有序性都靠volatile和synchronized来完成,那么编写代码会很繁琐,但日常Java开发中并没有感受到这一点,正是因为Java语言的“先行发生”原则。这个原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据。

先行发生是Java内存模型中定义的两项操作数之间的偏序关系,如果说操作A先行发生于操作B,就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。

下面是Java内存模型中一些“天然的”先行发生关系,这些先行发生关系无需任何同步协助器协作即可存在,可以直接在编码中使用。如果两个关系不在此列,而又无法通过这些关系推导出来,它们的顺序就无法保证,虚拟机可以对它们任意重排序。

程序次序法则:  线程中的每一个动作A都happens-before于该线程中的每一个动作B,其中,在线程中,所有的动作B都出现在动作A之后

管程锁定规则:  对于一个监视器锁的unLock 操作happens-before于每个后续对同一监视器锁的Lock操作

volatile变量法则:  对volatile域的写入操作happens-before于每个后续对同一个yu域的读操作。

线程启动法则:  在同一个线程里,对Thread.start的调用会happens-before于每一个启动线程中的动作。

线程终结法则:  线程中的所有动作都happens-before于其它线程检测到这个线程已经终结,或者从Thread.jonin调用成功返回,或者Thread.isAlive返回false.

中断法则:  一个线程调用另一个线程的interrupt happens-before 于被中断的线程发现中断(通过抛出InterruptedException 或者调用isInterrupted和interrupted)

终结法则:  一个对象的构造函数的结束happens-before于这个对象finalizer的开始

传递性:  如果 A happens-before 于 B,且 B happens-before 于 C,则 A happens-before 于C。

java无须任何手段即可保证上面的先行发生规则成立,下面那个例子看一下:

private int value = 0;

public void setValue(int value){
    this.value = value;
}

public int getValue(){
    return value;
}

假设A、B两个线程,线程A先(时间上的先后)执行setValue(1), 然后线程B调用同一对象的getValue(),那么线程B收到的返回值是什么?

依次分析一下先行发生原则中的各个原则:

由于两个方法分别在不同的线程中被调用,程序次序原则不适用;

没有同步块,自然不会发生lock和unlock操作,管程锁定原则不适用;

value变量没有被volatile修饰,volatile变量原则不适用;

后面的线程启动、中断、终止原则也毫无关系;

没有一个适用的原则,传递性也不适用。

所以说线程B得到的结果不确定是0还是1,换句话说,这里面的操作不是线程安全的。

怎么修复呢?getter/setter 定义synchronized方法;或者把value变量定义volatile变量,就回到了先行发生原则上了。

private volatile int value = 0;

另外,先行发生并不代表一定是先发生!

时间先后顺序于先行发生的原则之间基本没有太大的关系。

比如如下代码中,i, j 的取值问题:

//同一个线程中执行 
int i = 1; 
int j = 2;
// doSth...
volt = 10;  // 假设volt为 volatile 修饰的

根据程序次序规则,”int i = 1”的操作先行发生于”int j = 2”,但是”int j = 2”的代码完全可能先被处理器执行,这并不影响先行发生原则的正确性,因为我们在这条线程中并没有办法感知到这点。

先行发生原则,可以帮你判定是否并发安全的,从而不必去猜测是否是线程安全了!

---- 摘自《深入理解Java虚拟机》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK