标签:获得 动作 处理器 排序 down log 没有 写在前面 有序
一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系
happen-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。
happens-before原则定义:
1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
下面是happens-before规则:
1)一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
2)监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
3)volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
4)传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。
(happens-before以下部分转自【死磕Java并发】—–Java内存模型之happens-before)
我们来详细看看上面每条规则(摘自《深入理解Java虚拟机第12章》):
程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性。
锁定规则:这个规则比较好理解,无论是在单线程环境还是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作一定是happens-before读操作的。
传递规则:提现了happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C
线程启动规则:假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。
上面八条是原生Java满足Happens-before关系的规则,但是我们可以对他们进行推导出其他满足happens-before的规则:
这里再说一遍happens-before的概念:如果两个操作不存在上述(前面8条 + 后面6条)任一一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序。如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的。
JMM把happens- before要求禁止的重排序分为了下面两类:
1)会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
2)不会改变程序执行结果的重排序,JMM对编译器和处理器不作要求(JMM允许这种重排序,如as-if-serial)。
只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。
比如,如果编译器经过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁可以被消除。
再比如,如果编译器经过细致的分析后,认定一个volatile变量仅仅只会被单个线程访问,那么编译器可以把这个volatile变量当作一个普通变量来对待。
这些优化既不会改变程序的执行结果,又能提高程序的执行效率。
数据依赖性
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性
只要重排序两个操作的执行顺序,程序的执行结果将会被改变
编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
只针对单线程
as-if-serial语义
不管怎么重排序(编译器和处理器为了提高并行度),程序的执行结果不能被改变(只针对单线程)
编译器和处理器遵守数据依赖性原因:为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果
遵守as-if-serial语义,单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题
// 举例:可能A-->B--C 也可能B-->A-->C double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C
标签:获得 动作 处理器 排序 down log 没有 写在前面 有序
原文地址:https://www.cnblogs.com/hexinwei1/p/9897998.html