【多线程】voliate如何禁止指令重排

2024-01-01 04:04:13

voliate关键字

Java中的voliate关键字用于修饰变量
1、强制修饰的变量对应写操作立即刷新到主内存中
2、强制读操作从内存读取变量的最新值,避免读取旧值的情况
voliate关键字主要用于多线程程序中,防止出现数据同步问题。

voliate关键字作用

voliate只能保证变量可见性(可见性)和禁止指令重排(有序性),不能保证原子性。

保证变量的可见性

可见性:多个线程之间对共享变量的修改可以及时通知到其他线程。如果共享变量的可见性不能保证的话,那么就会可能出现数据不一致的情况。

禁止指令重排

CPU 存在乱序执行,Volatile 可以保证禁止指令重排(乱序执行)

指令重排

什么是指令重排

java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。指令重排是处理器为提高运算速度而做出违背代码原有顺序的优化。指令重排可以保证串行语义一致,否则我们的应用程序根本无法正常工作,但是没有义务保证多线程间的语义也一致。

Java内存模型(JMM)

说到指令重排,我们先来了解JMM模型。在多线程的世界里,都是围绕多线程的原子性、可见性、有序性来建立的。

(1)原子性(Atomicity)
原子性是指一个操作是不可中断的,即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。
(2)可见性(Visibility)
可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。对于串行程序来说可见性问题是不存在的,因为在任何一个操作步骤中修改了某个变量,那么后续的步骤中,读取这个变量的值,一定是修改之后的。但是在并行程序中,如果一个线程修改了某一个全局变量,那么其他线程未必可以马上知道这个改动。
(3)有序性(Ordering)
有序性是指在单线程环境中, 程序是按序依次执行的。而在多线程环境中, 程序的执行可能因为指令重排而出现乱序。

指令重排流程

在这里插入图片描述

指令重排带来的问题

public void methodA() {
// 以下两句执行顺序可能会在指令重排等场景下发生变化
a = 1;
flag = true;
}
public void methodB() {
if (flag) {
int i = a + 1;
……
}
}

假设线程A首先执行methodA()方法,接着线程B执行methodB()方法,如果发生指令重排,那个线程B在执行 int i = a + 1时,不一定能看见a已经被赋值为1了。

指令重排序规范

as-if-serial规范
As-if-serial语义的意思是,所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。

可以重排的情况,对于下面代码 , 两条指令顺序颠倒 , 执行结果相同 , 可以进行指令重排 ;

int a = 0;
int b = 0;

不可以进行重排的情况 : 对于下面的代码 , 两条指令如果上下颠倒 , 结果不同 , 不可以进行指令重排 ;

int a = 0;
int b = a;

Happen-Before先行发生原则规范
如果光靠sychronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性, 那么代码将会变得异常繁琐.
JMM提供了8个Happen-Before规则来约束数据之间是否存在竞争, 线程环境是否安全, 具体如下:
(1)顺序原则:一个线程内保证语义的串行性; a = 1; b = a + 1
(2)volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
(3)锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
(4)传递性:A先于B,B先于C,那么A必然先于C
(5)线程的start()方法先于它的每一个动作
(6)线程的所有操作先于线程的终结(Thread.join())
(7)线程的中断(interrupt())先于被中断线程的代码
(8)对象的构造函数执行结束先于finalize()方法

禁止指令重排:内存屏障 (LL LS SL SS)

内存屏障:又称内存栅栏,是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

文章来源:https://blog.csdn.net/xiazi0721/article/details/135319858
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。