本博客系列是学习并发编程过程当中的记录总结。因为文章比较多,写的时间也比较散,因此我整理了个目录贴(传送门),方便查阅。html
并发编程系列博客传送门java
本文是《深刻Java虚拟机》的部分读书笔记编程
若是Java内存模型中全部的有序性都仅靠volatile和synchronized来完成,那么有不少操做都将会变得很是啰嗦。安全
可是咱们在编写Java并发代码的时候并无察觉到这一点,这是由于Java语言中有一个“先行发生”(Happens-Before)的原则。这个原则很是重要,它是判断数据是否存在竞争,线程是否安全的很是有用的手段。依赖这个原则,咱们能够经过几条简单规则一揽子解决并发环境下两个操做之间是否可能存在冲突的全部问题,而不须要陷入Java内存模型苦涩难懂的定义之中。并发
先行发生是Java内存模型中定义的两项操做之间的偏序关系,好比说操做A先行发生于操做B,其实就是说在发生操做B以前,操做A产生的影响能被操做B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。app
下面是Java内存模型下一些“自然的”先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,能够在编码中直接使用。若是两个操做之间的关系不在此列,而且没法从下列规则推导出来,则它们就没有顺序性保障,虚拟机能够对它们随意地进行重排序。函数
Java语言无须任何同步手段保障就能成立的先行发生规则有且只有上面这些,下面演示一下如何使用这些规则去断定操做间是否具有顺序性,对于读写共享变量的操做来讲,就是线程是否安全。读者还能够从下面这个例子中感觉一下“时间上的前后顺序”与“先行发生”之间有什么不一样。学习
private int value = 0; pubilc void setValue(int value){ this.value = value; } public int getValue(){ return value; }
上面的代码显示的是一组再普通不过的getter/setter方法,假设存在线程A和B,线程A先(时间上的前后)调用了setValue(1),而后线程B调用了同一个对象的getValue(),那么线程B收到的返回值是什么?this
咱们依次分析一下先行发生原则中的各项规则。因为两个方法分别由线程A和B调用,不在一个线程中,因此程序次序规则在这里不适用;因为没有同步块,天然就不会发生lock和unlock操做,因此管程锁定规则不适用;因为value变量没有被volatile关键字修饰,因此volatile变量规则不适用;后面的线程启动、终止、中断规则和对象终结规则也和这里彻底没有关系。由于没有一个适用的先行发生规则,因此最后一条传递性也无从谈起,所以咱们能够断定,尽管线程A在操做时间上先于线程B,可是没法肯定线程B中getValue()方法的返回结果,换句话说,这里面的操做不是线程安全的。编码
咱们至少有两种比较简单的方案能够选择:要么把getter/setter方法都定义为synchronized方法,这样就能够套用管程锁定规则;要么把value定义为volatile变量,因为setter方法对value的修改不依赖value的原值,知足volatile关键字使用场景,这样就能够套用volatile变量规则来实现先行发生关系。
经过上面的例子,咱们能够得出结论:一个操做“时间上的先发生”不表明这个操做会是“先行发生”。那若是一个操做“先行发生”,是否就能推导出这个操做一定是“时间上的先发生”呢?很遗憾,这个推论也是不成立的。
上面两个例子综合起来证实了一个结论:时间前后顺序与先行发生原则之间基本没有因果关系,因此咱们衡量并发安全问题的时候不要受时间顺序的干扰,一切必须以先行发生原则为准。