Java 并发编程】线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )

网友投稿 579 2022-05-30

文章目录

总结

一、指令重排序规范

二、指令重排序示例

总结

Java 并发的 3 3 3 特性 :

原子性 : 每个操作都是

不可拆分的原子操作

; 在线程中进行 a++ 就不是原子操作 , 该操作分为 3 3 3 个步骤 , 首先从主内存中读取 a 变量 , 然后进行自增操作 , 最后在将自增后的值写回主内存中 ;

可见性 :

多个线程

访问同一个变量 , 该变量一旦被

某个线程修改

, 这些线程必须可以

立刻看到被修改的值 ;

有序性 : 程序按照

代码先后顺序

执行 ;

使用 volatile 关键字只能保证 可见性 和 有序性 , 但是不能保证原子性 ;

volatile 可以激活线程共享变量的 "

缓存一致性协议

" ; 保证

可见性 ;

volatile 可以 禁止 JVM 的 "

指令重排

" ; 保证

有序性 ;

一、指令重排序规范

指令重排指的是 , 线程中如果两行代码

没有逻辑上的上下关系

, 可以对代码进行

重新排序 ;

JVM 指令重排遵循规范 :

as-if-serial 规范 : 单个线程中, 指令的重排 ,

不能影响程序的执行结果 ;

可以重排的情况 : 对于下面代码 ,

两条指令顺序颠倒 , 执行结果相同 ,

可以进行指令重排 ;

x = 0; y = 1;

1

2

不可以进行重排的情况 : 对于下面的代码 ,

两条指令如果上下颠倒 , 结果不同 ,

不可以进行指令重排 ;

【Java 并发编程】线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )

x = 0; y = x;

1

2

happens-before 规范 : 先行发生原则 ;

二、指令重排序示例

指令重排示例 :

public class Main { // 使用 volatile 关键字修饰变量可以禁止指令重排 /*volatile static int x = 0; volatile static int y = 0; volatile static int a = 0; volatile static int b = 0;*/ // 没有使用 volatile 关键字修饰, 会产生指令重排的情况 static int x = 0; static int y = 0; static int a = 0; static int b = 0; /** * 多线程运行导致异常值出现, 是由于指令重排导致的 * @param args */ public static void main(String[] args) { // 设置一个非常大的遍历数 // 指令重排出现过程很少见, 基数越大, 出现概率越高 for (int i = 0; i < Integer.MAX_VALUE; i ++) { // 每次循环都初始化变量 x = 0; y = 0; a = 0; b = 0; // 在该线程中, 如果出现指令重排 // 先执行 b = 1, 在执行 x = a new Thread(new Runnable() { @Override public void run() { x = a; b = 1; } }).start(); // 如果出现指令重排 // 先执行 a = 1, 在执行 y = b new Thread(new Runnable() { @Override public void run() { y = b; a = 1; } }).start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } /* 执行上述代码, 不是线程 1 先执行就是线程 2 先执行 如果线程 1 先执行, 则 x = 0, y = 1 如果线程 2 先执行, 则 x = 1, y = 0 不可能出现 x = 1, y = 1 的情况 如果出现了, 则说明线程内部的执行顺序可能被颠倒了 出现了指令重排的情况 */ // 检查是否有异常值出现, 如果出现异常值, 退出方法 if (x == 1 && y == 1) { System.out.println("出现异常值 x = 1, y = 1"); return; } /*else { System.out.println("正常值 x = " + x + ", y = " + y); }*/ } } }

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

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

执行结果 :

两个线程的线程调度 :

协同式调度 : 线程执行时间 由 线程 决定 ;

抢占式调度 : 线程执行事件 由 系统 决定 ;

上述示例中的线程调度方式是 " 抢占式调度 " , 谁先执行

由系统分配

, 这两个线程的执行顺序都是随机的 , 可能线程 1 先执行 , 也可能是线程 2 先执行 ;

如果线程 1 先执行, 则 x = 0, y = 1 ;

如果线程 2 先执行, 则 x = 1, y = 0 ;

根据代码分析 , 不可能出现 x = 1, y = 1 的情况 , 如果出现了, 则说明

线程内部的执行顺序可能被颠倒了

, 出现了指令重排的情况 ;

Java 任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:博客技术资料整理
下一篇:Red&nbsp;Hat
相关文章