编译器和处理器为了提高程序的运行性能,对指令进行重新排序
虽然重排序能够优化性能,但是前提是必须保证结果正确,那么它是如何在单线程下保证结果正确的呢?是根据诗句的依赖性来做的,数据的依赖性可以分为三类:
写后读
写后读的操作肯定是不能重排的,例如a = 1然后b = a
这对于a来说是一个先写后读的过程,这个过程不能改变,如果变成b = a然后a = 1,那么结果也就变了,b最终得到的就不是1
读后写
读后写也不能重排,例如b = a然后a = 1,对于a来说这是一个读后写的操作,不能重排
重排后变成a = 1,b = a。那么结果就变了,最终b的值就是1了
写后写
写后写也是不能重排的,例如a = 1,a = 2。
如果重排,变成a = 2,a = 1。那么最后a的结果是1而不是2了
在单线程中不会有太大的影响,只会提升执行效率,但是在多线程中指令重排序会有较大的影响
例如:
public class Demo08 {
private int a;
private boolean flag;
public void writer() {
a = 1;
flag = true;
}
public void read() {
if (flag) {
int b = a + 1;
System.out.println(b);
}
}
}
在上面的代码中我们要做的事很明确,就是先将a设置为1,然后flag设置为true。然后再判断flag的值,这时候为true,会进if判断,然后b = a + 1,因此理想的情况下b等于2。
happens-beore是用来指定两个操作之间的执行顺序,提供跨线程的内存可见性
happens-before规则如下:
锁的释放与获取所建立的happens-before关系:锁的释放happens-before锁的获取
锁的释放和获取的内存语义:锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程和发送消息
volatile读写所建立的happens before关系:对于一个volatile域的写,happens-before于任意后续对这个volatile域的读
volatile读写的内存语义:当写一个volatile变量时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;当读一个volatile变量时,Java内存模型会把当前线程对应的本地内存中的共享变量重置为无效,然后从主内存中读取共享变量
在java中,被final修饰的类不能被继承,被final修饰的方法不能被重写,被final修饰的变量不能被改变
写final域的重排序规则:禁止把final域的写重排序到构造方法之外,也就是可以在构造方法中初始化final修饰的变量
public class Demo09 {
private final int b;
public Demo09() {
b = 10;
}
}
// 编译器会在final域的写之后,在构造方法执行完毕之前,插入一个内存屏障StoreStore,保证处理器把final域的写操作在构造方法之中完成
// 一般的内存屏障
// LoadLoad
// StoreStore
// LoadStore
// StoreLoad
读final域的重排序规则:在一个线程中,初次读对象引用和初次读该对象所包含的final域,Java内存模型禁止处理器重排序这两个操作
final域为静态类型:
final域为抽象类型:在构造方法内对一个final引用的对象的成员域的写入,与随后在构造方法外把这个被构造对象的引用赋值给一个引用变量,这个两个操作之间不能重排序
原文:https://www.cnblogs.com/Myarticles/p/12046101.html