Java并发编程:Java内存模型(2)

public static final int i; public final int j; static{ i = 0; } { //也可以在构造器初始化 j = 0; }  上面代码变量i和j都具有可见性,它们无须同步就能被其它线程正确访问到。

 

3.有序性(Ordering)即程序执行代码的先后顺序。

看下面的代码:

int i=0;  

boolean flag = false;  

i = 1;                    //语句1  

flag = true;          //语句2 

int i=0; boolean flag = false; i = 1; //语句1 flag = true; //语句2  JVM在真正执行这段代码时会按照先语句1、后语句2的顺序来执行吗,不一定,因为这里可能会发生指令重排序。

指令重排序:一般来说,处理器为了提高运行效率,会对运行的代码优化排序,它不保证各个语句的执行顺序与代码的先后顺序一致,但是它会保证程序的执行结果与顺序执行的结果一致。

那它是怎么来保证执行结果一致的呢,原因是它会考虑数据的依赖性。如果指令2必须要用到指令1的结果,那么处理器会保证指令1比指令2先执行。

指令重排序在单线程中是没有问题的,那在多线程中呢,就不一定了。看下面代码:

Map config;  

boolean flag = false;  

//假设线程1执行如下代码  

config = initConfig();//初始化config  

flag = true;  

//假设线程2执行如下代码  

while(!flag){//等待flag为true,代表线程1已经初始化完成  

     sleep();  

}  

//使用线程1初始化好的配置  

doSomethingWithConfig(); 

Map config; boolean flag = false; //假设线程1执行如下代码 config = initConfig();//初始化config flag = true; //假设线程2执行如下代码 while(!flag){//等待flag为true,代表线程1已经初始化完成 sleep(); } //使用线程1初始化好的配置 doSomethingWithConfig();  假如线程1在执行时,发生指令重排序,先执行了flag = true,后执行initConig(),因为两句没有依赖关系,是可以发生的。

然后线程2再执行时,就发生异常了,因为这时config根本没有初始化完成。

所以指令重排序不会影响单��线程的执行,但是会影响多线程并发执行的正确性。

Java语言提供volatitle和synchronized来保证线程之间操作的有序性。

volatitle本身就包含禁止指令重排序的语义,详细的后面再讲。

synchronized则是由“一个变量在同一个时刻只允许一个线程对其进行Lock操作”这条规则获得。

 

介绍完Java内存模型的三大特征,会不会觉得synchronized是万能的,的确大部分的并发控制操作都能使用synchronized来完成,但就是因为它的万能造就了程序员的滥用,越万能的并发控制,通常会带来越严重的性能影响。后面会讲到虚拟机锁的优化。

 

五、先行发生原则(Happens-before)

如果Java内存模型的所有有序性都靠volatitle和synchronized来完成,那么有一些操作会变得很烦锁,但是我们在编写java并发代码时并没感觉到这一点,这是因为java语言有一个“先行发生”(happens-before) 原则。这个原则非常重要,这是判断数据是否竞争,线程是否安全的重要依据。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/ce3143efcb3bf7b801065db1a14678aa.html