什么是重排序?
执行任务的时候,为了提高编译器和处理器的执行性能,编译器和处理器(包括内存系统,内存在行为没有重排但是存储的时候是有变化的)会对指令重排序。编译器优化的重排序是在编译时期完成的,指令重排序和内存重排序是处理器重排序
- 编译器优化的重排序,在不改变单线程语义的情况下重新安排语句的执行顺序
- 指指令级并行重排序,处理器的指令级并行技术将多条指令重叠执行,如果不存在数据的依赖性将会改变语句对应机器指令的执行顺序
- 内存系统的重排序,因为使用了读写缓存区,使得看起来并不是顺序执行的
重排序会产生什么问题
- 重排序可能会导致多线程程序出现内存可见性问题。(工作内存和主内存,编译器处理器重排序导致的可见性)
- 重排序会导致有序性问题,程序的读写顺序于内存的读写顺序不一样(编译器处理器重排序,内存缓冲区(是处理器重排序的内容))
处理器重排序规则
处理器重排序规则,编译器在生成的指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。
- 读读 LoadLoad Barriers Load1 LoadLoad load2
确保Load1数据的装载先于Load2以及后面装载指令的装载 - 写写 StoreStore Barriers Store1 StoreStore Store2
确保Store1的写先于Store2以及后面的写指令的写入,写操作修改的内容对后面的写操作都是可见的 - 读写 LoadStore Barriers Load1 loadStore Store2
确保Load1的数据装载会优先于Store2以及后面写指令的写入读操作要在写操作的前面 - 写读 StoreLoad Barriers Store1 StoreLoad Load2
写操作做出的修改对读操作以及后面的操作是可见的,发生在后面的读操作和写操作之前。Store会使得该屏障之前的所有读写操作完成之后才会执行该屏障的访问指令
StoreLoad是一个全能型的屏障,它同时具有其他三个屏障的效果,现代处理器大多数支持该屏障,
该屏障消耗的资源较多,每一步都是把操作对应的数据刷新到内存中
什么是happen-before
java内存模型中提供的happens-before辅助保证程序执行的原子性可见性和有序性的问题,是判断线程是否存在竞争,线程是否安全的依据
程序顺序原则,一个线程内要按照代码的顺序执行,保证语义的串行性
锁规则,解锁操作必须发生在对同一个锁的加锁操作之前
volatile规则,对volatile修饰的变量的写操作发生在volatile变量的读操作之前。访问volatile的变量时候强制从主内存中进行读取,volatile修饰的变量被修改之后会被强制的刷新到主内存中。所以说volatile修饰的变量保证了可见性
线程启动规则,线程的start()方法最先执行,线程A在线程B执行start()方法之前对共享变量的修改对线程B可见
线程终止规则,线程的所有操作优于线程的终结,Thread.join()方法的作用是等待当前线程终止,线程B在终止之前修改了共享变量,执行完成后,线程B对共享变量的修改将对线程A可见
线程中断规则,对线程interrupt中断方法的调用发生在被中断的线程检测到中断事件的发生之前
对象终结规则,对象的构造函数执行完成先于finalize()方法
传递性,线程A生在线程B之前,线程B发生在线程C之前,则线程A发生在线程C之前
java内存模型提供了8种happen-before规则,帮助实现原子性可见性和有序性,在执行程序的时候查看是否满足happen-before的8种规则,如果满足的话就能够保证线程安全
class MixedOrder{
int a=0;
boolean flag=false;
public void writer(){
a=1;
flag=true;
}
}
public void read(){
if(flag){
int i=a+1;
}
}
此时如果存在两条线程A和B,线程A调用实例方法writer(),线程B调用read()方法,线程A先调用,而线程B后调用,那么线程B读取到的i值是多少?
判断:
因为存在两条线程,程序顺序规则不满足,因为writer()方法和read()方法没有都没有使用同步手段,锁的规则也不适用,没有使用volatile修饰变量,volatile规则也不不适应,线程A和线程B也不适用,线程的启动规则,线程的终止规则,现成的中断规则,传递性,对象终结规则也不可以用,happend-before原则不适用同时也没有使用synchronized或者变量添加volatile关键字,因此不能保证线程安全。
总结:happen-before 中的8条规则,运用在程序中要进行判断