logo头像
书院的十三先生

Java并发编程(四)有序性

一、指令重排

在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {

private static Singleton singleton;

private Singleton() {}

public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
}
}

return singleton;
}
}

对于如下代码:

1
singleton = new Singleton();

我们以为的顺序是:
1.分配一块内存
2.在内存上初始化Singleton对象
3.然后M的地址赋值给instance变量

但实际上不是,查看JAVA字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class com.javashizhan.concurrent.demo.base.Singleton {
public static com.javashizhan.concurrent.demo.base.Singleton getInstance();
Code:
0: aconst_null
1: getstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
4: if_acmpne 39
7: ldc #3 // class com/javashizhan/concurrent/demo/base/Singleton
9: dup
10: astore_0
11: monitorenter
12: aconst_null
13: getstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
16: if_acmpne 29
19: new #3 // class com/javashizhan/concurrent/demo/base/Singleton
22: dup
23: invokespecial #4 // Method "<init>":()V
26: putstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
29: aload_0
30: monitorexit
31: goto 39
34: astore_1
35: aload_0
36: monitorexit
37: aload_1
38: athrow
39: getstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
42: areturn
Exception table:
from to target type
12 31 34 any
34 37 34 any
}

看19~26,实际的顺序是:
1.分配一块内存
2.将M的地址赋值给instance变量
3.最后在内存M上初始化Singleton对象。

由于指令的顺序问题,多线程并发时线程A执行到26之前发生了线程切换,此时线程B发现null == singleton不成立,获取到singleton,而此时singleton并没有初始化完,就会引发空指针异常。

二、Happens-Before 规则

Happens-Before 约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守Happens-Before 规则。

1.程序的顺序性规则
在一个线程中,按照程序顺序,前面的操作Happens-Before于后续的任意操作。程序前面对某个变量的修改一定是对后续操作可见的。

2.volatile变量规则
对一个volatile变量的写操作,Happens-Before于后续对这个volatile变量的读操作。

3.传递性
如果A Happens-Before于B,B Happens-Before于C,那么A Happens-Before于C。

4.锁的规则
对一个锁的解锁Happens-Before于对这个锁的加锁。

5.线程Start规则
主线程A启动子线程B后,子线程B能看到主线程在启动子线程B之前的操作。

6.线程join规则
主线程A等待子线程B完成(主线程通过调用子线程B的join方法实现),当子线程B完成后(主线程A中join()方法返回),主线程能够看到子线程的操作。

7.线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

8.对象终结规则
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

end.


站点: http://javashizhan.com/


微信公众号: