volatile 的实现机制和使用场景

x33g5p2x  于2022-04-10 转载在 其他  
字(1.1k)|赞(0)|评价(0)|浏览(185)

一 实现机制

volatile 关键字可以保证可见性和顺序性,那么它是如何做到的呢?

volatile 底层是通过内存屏障机制实现的,该内存屏障会为指令的执行提供如下几个保障。

  • 确保指令重排时不会将其后面的代码排到内存屏障之前。
  • 确保指令重排时不会将其前面的代码排到内存屏障之后。
  • 确保在执行到内存屏障修饰的指令时前面的代码全部执行完成。
  • 强制将线程工作内存中的值的修改刷新到主内存中。
  • 如果是写操作,则会导致其他线程工作内存(CPU Cache)中的缓存数据失效。

二 volatile 的使用场景

虽然 volatile 有部分 synchronized 关键字语义,但是 volatile 不可能完全替代 synchronized 关键字,因为 volatile 关键字不具备原子性操作语义,我们在使用 volatile 关键字的时候也是充分利用它的可见性以及有序性(防止重排序)特点。

1 开关控制利用可见性的特点

package concurrent;

public class ThreadCloseable extends Thread {
    // volatile 关键字保证了 started 线程的可见性
    private volatile boolean started = true;

    @Override
    public void run() {
        while (started) {
            // do work
        }
    }

    public void shutdown() {
        this.started = false;
    }
}

当外部线程执行 ThreadCloseable 的 shutdown 方法时,ThreadCloseable 会立刻看到 started 发生了变化,原因是 ThreadCloseable 工作内存中的 started 失效了,不得不到主内存中重新获取。

如果 started 没有被 volatile 关键字修饰,那么很有可能外部线程在其工作内存中修改了 started 之后不及时刷新到主内存,或者 ThreadCloseable 一直到自己的工作内存中读取 started 变量,因为无法感知 started 的变化,导致线程无法关闭。

2 状态标记利用顺序性特点

private volatile boolean initialized = false;
private Context context;
public Context load(){
    if(!initialized){
        context = loadContext();
        initialized = true; // 阻止重排序
    }
    return context;
}

initialized 加了 volatile 会防止指令重排序

3 单例设计模式的 double-check 也是利用了顺序性特点。

相关文章