Java 并发编程线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 并发编程线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )相关的知识,希望对你有一定的参考价值。
总结
Java 并发的 3 3 3 特性 :
- 原子性 : 每个操作都是 不可拆分的原子操作 ; 在线程中进行
a++
就不是原子操作 , 该操作分为 3 3 3 个步骤 , 首先从主内存中读取a
变量 , 然后进行自增操作 , 最后在将自增后的值写回主内存中 ; - 可见性 : 多个线程 访问同一个变量 , 该变量一旦被 某个线程修改 , 这些线程必须可以 立刻看到被修改的值 ;
- 有序性 : 程序按照 代码先后顺序 执行 ;
使用 volatile 关键字只能保证 可见性 和 有序性 , 但是不能保证原子性 ;
volatile 可以激活线程共享变量的 " 缓存一致性协议 " ; 保证 可见性 ;
volatile 可以 禁止 JVM 的 " 指令重排 " ; 保证 有序性 ;
一、指令重排序规范
指令重排指的是 , 线程中如果两行代码 没有逻辑上的上下关系 , 可以对代码进行 重新排序 ;
JVM 指令重排遵循 as-if-serial 规范 : 单个线程中, 指令的重排 , 不能影响程序的执行结果 ;
- 可以重排的情况 : 对于下面代码 , 两条指令顺序颠倒 , 执行结果相同 , 可以进行指令重排 ;
x = 0;
y = 1;
- 不可以进行重排的情况 : 对于下面的代码 , 两条指令如果上下颠倒 , 结果不同 , 不可以进行指令重排 ;
x = 0;
y = x;
二、指令重排序示例
指令重排示例 :
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 先执行 ;
如果线程 1 先执行, 则 x = 0, y = 1 ;
如果线程 2 先执行, 则 x = 1, y = 0 ;
根据代码分析 , 不可能出现 x = 1, y = 1 的情况 , 如果出现了, 则说明 线程内部的执行顺序可能被颠倒了 , 出现了指令重排的情况 ;
以上是关于Java 并发编程线程指令重排序问题 ( 指令重排序规范 | volatile 关键字禁止指令重排序 )的主要内容,如果未能解决你的问题,请参考以下文章
Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)
Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)