在多线程并发编程中,编译器和处理器为了提升执行效率,可能会对指令进行重排序,这种优化在单线程场景下不会出现问题,但在多线程场景下可能导致共享变量的可见性和有序性问题。通过while循环配合自定义内存屏障,可以实现基础的指令顺序保护逻辑,避免重排序带来的异常。

指令重排序与内存屏障基础
指令重排序是指编译器和处理器在不改变单线程程序语义的前提下,重新安排指令的执行顺序。比如下面的代码,在多线程场景下可能出现问题:
// 线程A执行
int a = 1;
boolean flag = true;
// 线程B执行
while (!flag) {
// 等待flag变为true
}
System.out.println(a);
由于指令重排序,线程A中的flag = true可能被提前执行,导致线程B在a = 1执行前就跳出循环,最终打印出a的初始值0,而不是预期的1。
内存屏障是一种硬件或软件层面的指令,用于禁止特定类型的指令重排序,保证屏障前后的指令按照程序顺序执行。自定义内存屏障通常是基于特定语言或平台提供的基础屏障能力封装而成。
自定义内存屏障的实现
不同语言实现自定义内存屏障的方式不同,以Java为例,可以基于Unsafe类提供的屏障方法封装,也可以基于volatile关键字的特性实现简单的自定义屏障。下面是基于Unsafe实现的自定义内存屏障示例:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CustomMemoryBarrier {
private static final Unsafe UNSAFE;
private static final long STATE_OFFSET;
static {
try {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
UNSAFE = (Unsafe) unsafeField.get(null);
STATE_OFFSET = UNSAFE.objectFieldOffset(CustomMemoryBarrier.class.getDeclaredField("state"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 用volatile修饰保证可见性,同时作为自定义屏障的关联变量
private volatile int state = 0;
// 自定义写屏障,在写入state后插入屏障
public void writeWithBarrier(int value) {
state = value;
// 插入写屏障,禁止之前的写操作和屏障之后的写操作重排序
UNSAFE.storeFence();
}
// 自定义读屏障,在读取state前插入屏障
public int readWithBarrier() {
// 插入读屏障,禁止之后的读操作和屏障之前的读操作重排序
UNSAFE.loadFence();
return state;
}
}
while循环配合自定义内存屏障实现指令顺序保护
结合上面的自定义内存屏障类,我们可以用while循环配合屏障实现指令顺序保护,避免之前的重排序问题。完整示例如下:
public class InstructionOrderProtect {
private static final CustomMemoryBarrier BARRIER = new CustomMemoryBarrier();
private static int a = 0;
public static void main(String[] args) throws InterruptedException {
// 线程A:先给a赋值,再通过屏障写入标记
Thread threadA = new Thread(() -> {
a = 1;
// 写入标记时携带写屏障,保证a=1的写操作不会被重排序到state写入之后
BARRIER.writeWithBarrier(1);
});
// 线程B:循环等待标记变为1,读取时携带读屏障
Thread threadB = new Thread(() -> {
// while循环等待标记满足条件
while (BARRIER.readWithBarrier() != 1) {
// 空循环等待
}
// 此时a的赋值已经完成,不会读到初始值0
System.out.println("a的值为:" + a);
});
threadB.start();
// 保证线程B先启动,先进入等待状态
Thread.sleep(100);
threadA.start();
threadA.join();
threadB.join();
}
}
逻辑说明
- 线程A中,
a = 1的写操作在writeWithBarrier的state = value之前,写屏障保证了a = 1不会重排序到state写入之后,也就是说state变为1的时候,a = 1一定已经执行完成。 - 线程B中,while循环每次读取
state时都调用readWithBarrier,读屏障保证了读取state的操作不会被重排序,当读取到state为1时,线程A的写操作已经全部可见。 - while循环在这里的作用是等待共享标记满足条件,配合内存屏障保证了等待过程中指令的执行顺序符合预期,最终线程B一定能读到a的正确值1。
注意事项
自定义内存屏障的实现依赖具体的运行平台和语言特性,不同场景下的屏障类型选择也不同,比如写操作对应写屏障,读操作对应读屏障,读写混合场景可能需要全屏障。另外while循环等待时如果长时间不满足条件,会占用CPU资源,实际场景中可以结合休眠或者等待通知机制优化,但核心的指令顺序保护逻辑依然可以沿用上述思路。