}
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) 原则。这个原则非常重要,这是判断数据是否竞争,线程是否安全的重要依据。