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

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 并发编程线程共享变量可见性 ( volatile 关键字使用场景分析 | MESI 缓存一致性协议 | 总线嗅探机制 )相关的知识,希望对你有一定的参考价值。





一、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 1000 1000 毫秒后 , 子线程才修改 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 ;

以上是关于Java 并发编程线程共享变量可见性 ( volatile 关键字使用场景分析 | MESI 缓存一致性协议 | 总线嗅探机制 )的主要内容,如果未能解决你的问题,请参考以下文章

Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)

Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)

Java并发编程之volatile

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

[Java并发编程实战] 共享对象之可见性

[Java并发编程实战] 共享对象之可见性