Java多线程有序性-动力节点

Posted wkcto

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程有序性-动力节点相关的知识,希望对你有一定的参考价值。

有序性(Ordering)是指在什么情况下一个处理器上运行的一个线程所执行的 内存访问操作在另外一个处理器运行的其他线程看来是乱序的(Out of Order)。

乱序是指内存访问操作的顺序看起来发生了变化。

重排序

在多核处理器的环境下,编写的顺序结构,这种操作执行的顺序可能是没有保障的:

编译器可能会改变两个操作的先后顺序;

处理器也可能不会按照目标代码的顺序执行;

这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序。

重排序是对内存访问有序操作的一种优化,可以在不影响单线程程序正确的情况下提升程序的性能.但是,可能 对多线程程序的正确性产生影响,即可能导致线程安全问题。

重排序与可见性问题类似,不是必然出现的。

与内存操作顺序有关的几个概念:

源代码顺序, 就是源码中指定的内存访问顺序。

程序顺序, 处理器上运行的目标代码所指定的内存访问顺序。

执行顺序,内存访问操作在处理器上的实际执行顺序。

感知顺序,给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序。

可以把重排序分为指令重排序与存储子系统重排序两种:

指令重排序主要是由JIT编译器,处理器引起的, 指程序顺序与执行顺序不一样。

存储子系统重排序是由高速缓存,写缓冲器引起的, 感知顺序与执行顺序 不一致。

指令重排序

在源码顺序与程序顺序不一致,或者 程序顺序与执行顺序不一致的情况下,我们就说发生了指令重排序(Instruction Reorder)。

指令重排是一种动作,确实对指令的顺序做了调整, 重排序的对象指令。

javac编译器一般不会执行指令重排序, 而JIT编译器可能执行指令重排序。

处理器也可能执行指令重排序, 使得执行顺序与程序顺序不一致。

指令重排不会对单线程程序的结果正确性产生影响,可能导致多线程程序出现非预期的结果。

存储子系统重排序

存储子系统是指写缓冲器与高速缓存。

高速缓存(Cache)是CPU中为了匹配与主内存处理速度不匹配而设计的一个高速缓存。

写缓冲器(Store buffer, Write buffer)用来提高写高速缓存操作的效率。

即使处理器严格按照程序顺序执行两个内存访问操作,在存储子系统的作用下, 其他处理器对这两个操作的感知顺序与程序顺序不一致,即这两个操作的顺序顺序看起来像是发生了变化, 这种现象称为存储子系统重排序。

存储子系统重排序并没有真正的对指令执行顺序进行调整,而是造成一种指令执行顺序被调整的现象。

存储子系统重排序对象是内存操作的结果。

从处理器角度来看, 读内存就是从指定的RAM地址中加载数据到寄存器,称为Load操作; 写内存就是把数据存储到指定的地址表示的RAM存储单元中,称为Store操作.内存重排序有以下四种可能:

● LoadLoad重排序,一个处理器先后执行两个读操作L1和L2,其他处理器对两个内存操作的感知顺序可能是L2->L1。

● StoreStore重排序,一个处理器先后执行两个写操作W1和W2,其他处理器对两个内存操作的感知顺序可能是W2->W1。

● LoadStore重排序,一个处理器先执行读内存操作L1再执行写内存操作W1, 其他处理器对两个内存操作的感知顺序可能是W1->L1。

● StoreLoad重排序,一个处理器先执行写内存操作W1再执行读内存操作L1, 其他处理器对两个内存操作的感知顺序可能是L1->W1。

内存重排序与具体的处理器微架构有关,不同架构的处理器所允许的内存重排序不同。

内存重排序可能会导致线程安全问题.假设有两个共享变量int data = 0; boolean ready = false;

处理器1

处理器2

data = 1; //S1

ready = true; //S2

while( !ready){} //L3

sout( data ); //L4

貌似串行语义

JIT编译器,处理器,存储子系统是按照一定的规则对指令,内存操作的结果进行重排序, 给单线程程序造成一种假象----指令是按照源码的顺序执行的.这种假象称为貌似串行语义. 并不能保证多线程环境程序的正确性。

为了保证貌似串行语义,有数据依赖关系的语句不会被重排序,只有不存在数据依赖关系的语句才会被重排序.如果两个操作(指令)访问同一个变量,且其中一个操作(指令)为写操作,那么这两个操作之间就存在数据依赖关系(Data dependency)。

如:

x = 1; y = x + 1; 后一条语句的操作数包含前一条语句的执行结果;
y = x; x = 1; 先读取x变量,再更新x变量的值;
x = 1; x = 2; 两条语句同时对一个变量进行写操作

如果不存在数据依赖关系则可能重排序,如:

double price = 45.8;
int quantity = 10;
double sum = price * quantity;

存在控制依赖关系的语句允许重排.一条语句(指令)的执行结果会决定另一条语句(指令)能否被执行,这两条语句(指令)存在控制依赖关系(Control Dependency). 如在if语句中允许重排,可能存在处理器先执行if代码块,再判断if条件是否成立。

保证内存访问的顺序性

可以使用volatile关键字, synchronized关键字实现有序性。





以上是关于Java多线程有序性-动力节点的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程的三大特性,线程池,JMM(Java内存模型)

Java多线程——Volatile关键字保证可见性,有序性,禁止指令重排实现

多线程--线程安全

多线程--线程安全

Java多线程并发09——如何实现线程间与线程内数据共享

多线程编程 之java内存模型(JMM)可见性有序性问题解决方案