【Java 并发编程】线程共享变量可见性 ( volatile 关键字使用场景分析 | MESI 缓存一致性协议 | 总线嗅探机制 )

文章目录

一、volatile 关键字场景分析

二、缓存一致性协议 ( 总线嗅探机制 )





一、volatile 关键字场景分析


volatile 关键字使用场景 :


public class Main {
    private static volatile boolean flag = false;
    private static void changeFlag() {
        System.out.println("修改标志位开始");
        flag = true;
        System.out.println("修改标志位结束");
    }
    public static void main(String[] args) {
        // 在该线程中 , 1 秒后修改标志位为 false
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                changeFlag();
            }
        }.start();
        // 此处如果 flag 一直为 flase 就会进入死循环
        //      如果 flag 为 true 则程序结束
        while (!flag) {
        }
        System.out.println("主线程结束");
    }
}


完整场景分析 :


主线程将 flag 变量加载到自己的 工作内存 中 , 进行循环操作 ;


子线程将 flag 变量加载到自己的 工作内存 中 , 将 flag 设置为了 true , 然后将 改变的值设置到主内存 中 ; 此时主内存中的 flag 已经变为了 true ;


但是 主线程中的工作内存 中的 flag 变量副本还是 false , 因此还处于不断的循环中 ;


子线程修改了 flag 的值 , 但是 一直没有同步到主线程中 ;


如果主线程在子线程修改 flag 变量之后取值 , 那么取到的值就是修改后的值 , 程序可以结束 ;


但是主线程先将没有修改的 boolean flag = false 取走了 , 1000 10001000 毫秒后 , 子线程才修改 boolean flag = true , 主线程仍然使用 boolean flag = false 的副本 , 没人通知主线程修改该值 ;



使用了 volatile 关键字之后 , 如果 子线程修改了 flag 共享变量值 , 主线程也会改变线程工作内存中缓存的值 ;


通过 " 缓存一致性协议 " 机制 , 进行线程共享变量同步的操作 ;






二、缓存一致性协议 ( 总线嗅探机制 )


CPU 对 主内存 中的共享变量进行操作时 , 先将主内存中的共享变量值加载到 高速缓存 中 , 每个处理器核心都有自己的缓存 , 各个缓存之间的读写操作有一定的差异 , 为了 保证 CPU 高速缓存 与 主内存 中数据一致 , 就有了 " 缓存一致性协议 " ;



缓存一致性协议 MESI ( Modified Exclusive Shared Invalid ) :


M 修改 Modified : 数据在 线程工作内存中被修改 ;

E 独占 Exclusive : 数据被加载到线程工作内存过程中 , 标记为独占状态 ;

S 共享 Shared : 数据修改 同步到主内存中完成后 , 标记为共享状态 ; 共享状态的变量才能被线程加载到工作内存中 ;

I 失效 Invalid : 线程 A , B 中都使用了同一个共享变量 , 如果在线程 A 中修改该变量 , 线程 B 中的变量更新前都标记为失效状态 ;

MESI 的主要作用就是 对数据进行状态标记 , 根据标记判断对数据的操作 ;



将 boolean flag 标记为 volatile 之后的场景 :


volatile boolean flag


子线程将 flag 变量读取到线程工作内存中 , 然后将其赋值到线程的副本变量 flag 中 , 在子线程中操作修改该 flag 变量值 , 只要发现该 flag 副本变量值发生了修改 , 就会立刻向主内存中同步该值 , 不需要等到线程结束 ;


CPU 将主内存中的数据读取到线程工作内存中 , 这里的 主内存 就是计算机的 物理内存 , 线程工作内存 是 CPU 的高速缓存 , 读取操作是通过计算机中的 BUS 总线 进行的 ;


在 BUS 总线上 , 存在一个 " 总线嗅探机制 " , 一旦某个线程共享变量被声明为 volatile 变量之后 , 一旦在某个线程中 , 修改了该共享变量值 , 就会向 BUS 总线发送一个 共享变量改变的消息 ;


CPU 不停地嗅探 BUS 总线 上的 " 共享变量改变的消息 " , 一旦接收到该消息事件 , 就会将正在该 CPU 核心上执行的 其它线程 的 工作内存 的 变量副本 置为失效 ( Invalid ) 状态 ; 之后该 CPU 核心 会立刻向 BUS 总线 发送一条消息 , 表示 该线程中的副本变量已经失效 ;


子线程收到主线程副本变量失效的消息 , 此时会 将共享变量 flag 变量加锁 , 独占该共享变量 , 然后将已经改变的 flag 变量 写出到主内存中 ;


子线程中将线程共享变量 flag 写出到主内存完毕后 , 会 解锁该变量 , 然后 主线程就会从主内存中加载线程共享变量 flag ;


上一篇:mysql – 处理删除/插入/选择一个巨大的表


下一篇:MongoDB Shard部署及Tag的使用