Java多线程安全问题之有序性(下)

176 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

3.存储子系统重排序

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

高速缓存(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

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

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

boolean ready = false;

image.png

4.貌似串行语义

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 条件是否成立

5. 保证内存访问的顺序性

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

Java 内存模型

image.png