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

简介: 【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 ;


目录
相关文章
|
4月前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
67 0
|
4月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
73 0
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
46 4
|
1月前
三种线程的使用场景
三种创建多线程的使用场景 1、继承的方式:适合于这个任务只想被一个线程的对象执行的情况 2、实现Runnable接口方式:适合于一个任务想被多个线程执行的情况 3、实现Callable接口方式:也适合一个任务想被多个线程执行的情况,你还想得倒任务的执行结果
23 0
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
28 2
|
2月前
|
缓存 Java 编译器
【多线程-从零开始-伍】volatile关键字和内存可见性问题
【多线程-从零开始-伍】volatile关键字和内存可见性问题
44 0
|
4月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
71 1
|
4月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
5月前
|
存储 安全 Java
Java面试题:深入探索Java内存模型,Java内存模型中的主内存与工作内存的概念,Java内存模型中的happens-before关系,volatile关键字在Java内存模型中的作用
Java面试题:深入探索Java内存模型,Java内存模型中的主内存与工作内存的概念,Java内存模型中的happens-before关系,volatile关键字在Java内存模型中的作用
43 1
|
5月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
58 0
下一篇
DataWorks