</pre>理解一下数据依赖性<p></p><p><strong> </strong> 如果有两个操作,访问同一个变量并且有一个操作是写操作,那这两个操作存在数据依赖。</p><p> 一般有如下几种情况:</p><p> 1.写后写:a=1;a=2;</p><p> 2.写后读:a=1;b=a;</p><p> 3.读后写:b=a;a=1;</p><p>如上几种情况都存在依赖关系。存在依赖关系的操作就无法重排序。重排序会导致结果不一致。</p><p>例子:</p><pre name="code" class="java"></pre><pre name="code" class="java">if(flag){//1
a=a+1;//2
}
上述例子是否存在数据依赖,是否可以重排序?
答案:根据上述的数据以来的概念,1根2不存在数据以来可以重排序。why?
因为如果重排序了,编译器和处理器会采用中间变量来管理这两行代码。先执行2 ,mod = a+1; 在执行1,如果flag=true,则继续a=mod.把计算的过程提前处理了。
前面有提到过几种重排序方法,有编译器重排序,指令级重排序,内存系统重排序,其中2跟3属于处理器重排序。编译器和处理器都会根据数据依赖进行重排序。
注意一点,这边所说的数据依赖是真对单个处理器和单个内存指令,不同处理器不同线程的数据依赖,处理器和编译器考虑不到。
所以,重排序会在单线程中保持一致的结果(as -if -serial)。而多线程则不保证结果一致。
as -if -serial语义
as -if -serial语义的意思是不管怎么重排序(为了提高处理器和编译器的性能和并发度),程序的执行结果都是一致的,当然前提是在一个单线程内。编译器和处理器也都遵循这个规则。
举个栗子为什么存在数据依赖的不能进行重排序。
a = 2;//A
b = 3;//B
c = a+b;//C
上面ABC的依赖关系是AC,BC存在依赖关系,所以C跟AB不能进行重排序,否则会影响结果。所以C只能在AB的后面执行。但是AB之间却不存在数据以来关系,所以执行的顺序可能是ABC ,BAC这两种情况。
as -if -serial语义把单线程重排序的结果保护起来,让程序可以执行到一致的结果。
多线程中为什么不可以重排序?
我们来分析一个例子。
public class Test {
int a = 0;
boolean flag = false;
public void A(){
a=1;//1
flag=true;//2
}
public void B(){
if(flag){//3
a=a+1;//4
}
}
}
如上代码,假如我们有两个线程一个执行A方法,一个执行B方法,如果A方法因为两个操作都不存在数据依赖,所以先执行2(flag=true),然后这个时候如果程序片跳到T2,这个时候开始执行3跟4,a的结果就等于1,而按照正常的逻辑应该是a=2.所以多线程破坏了as-if-serial语义。
依据重排序,还有多种重排序的执行顺序,3根4也可以进行重排序,4的值先存在一个临时中间值。
所以在单线程中,重排序不会影响语义。而多线程则会影响重排序的语义,会导致结果不一致。
了解两个知识点
原子操作:原子操作是指不会被线程调度打断的操作,这种操作一旦开始就会一直运行到结束,中间没有没有context weitch.通俗的讲就是只有一个操作,不能打断的操作。例子:
int a = 1;//原子操作,一旦执行到这个语句,就只能全部执行完
a=a++;//不是原子操作
a++可以分成三个步骤
1.读取a
2.a+1
3.再把a+1赋值到a
long和double的类型是和进行存储的是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch