1、指令执行过程
2、共享变量
3、缓存不一致问题
4、原子性问题
5、可见性问题
6、有序性问题
7、synchronized、Lock
8、java默认的有序性原则
1、指令执行过程
a)从主存中获取变量值
b)复制该值到高速缓存
c)将高速缓存中的值给CPU计算并将结果写入高速缓存
d)将高速缓存中的计算结果写入主存
2、共享变量
多线程公用的变量
3、缓存不一致问题
多线程情况下出现该问题,产生的原因为:
a)线程1将变量x读取至自己的缓存中并准备计算
b)在线程1尚未将返回结果写入内存前,线程2将变量x的值读取到自己的缓存中,也准备计算。(此时线程2的缓存中x的值并不是线程1的计算结果)
c)线程1计算完成后将x的值写入内存
d)线程2计算完成后将x的值写入内存,此时会覆盖线程1的计算结果
两个线程运行结束后发现,线程1的计算其实被覆盖了,相当于无效,从而导致了计算错误。
4、原子性问题
一次运行,要么完成,要么处于操作前的状态
例如:int x=10; //二进制表示 (H)0000 0000 0000 0000 | (L)0000 0000 0000 1010
假设这个复制过程分成两个步骤:1)存高16位的值 2)存低16位的值
那么可能存在的情况:高16位存成功后另一个线程就来都x的值,此时低16位尚未存入,得到的x=0,显然是错误的。
当然,这只是为了说明问题,在几乎所有的语言中简单的复制操作都是遵循原子性操作的,也就是说:只有高低16位都存成功后别的线程才能获取到x的值。
5、可见性问题
共享变量的值修改后会通知其他线程该变量在其内存中的值无效
继续3中缓存不一致的问题:
a)线程1将变量x读取至自己的缓存中并准备计算。
b)在线程1尚未将返回结果写入内存前,线程2将变量x的值读取到自己的缓存中,也准备计算。(此时线程2的缓存中x的值并不是线程1的计算结果)
c)线程1计算完成改变了x的值,此时如果该操作遵循可见性则会通知线程2:x在的值已经改变,宣布线程2中的x的值无效。
d)线程1将计算后的x存入内存中。
d)线程2从内存中重新获取x的值并用于计算并将结果存入内存。
两个线程的运行都是有效的
6、有序性问题
有序:代码按照先后顺序运行
指令重排列:CPU在执行指令时处于优化考虑会将代码进行重排列
例如:
int i=0;
boolean runFlag=false;
i=100; //语句1
runFlag=true; //语句2
在执行时语句2可能比语句1先执行,但不管怎么重排列,CPU会保证执行的结果一致。这在单线程中不会有问题,在多线程中情况就不一样了。
例如:
线程1:
initParams(); // 1 初始化参数操作
boolean runFlag=true; // 2
线程2:
while(!runFlag){
sleep(100);
}
doSomethingWithParams(); //3 需要用到初始化后的参数
如果两个线程都开启并正常运行,可能存在的情况如下:
语句2别语句1先执行,线程2发现runFlag=true便会跳出等待循环而去执行doSomethingWithParams(),而此时initParams()尚未执行
便会使得doSomethingWithParams()使用的参数并不是初始化后的,这往往导致执行结果莫名其妙又不报错
7、synchronized、Lock
代码同步和锁都能保证变量的可见性和代码的有序性,因为它们保证了同一变量、方法或者代码块同一时间只有一个线程可以修改
8、java默认的有序性原则
happens-before原则:
1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
2)锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
5)线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
参考:http://www.importnew.com/18126.html