【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 ;


目录
相关文章
|
9天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
8天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
34 1
|
17天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
16天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
92 1
|
2月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
51 2
数据的存储--Redis缓存存储(二)
|
2月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
78 6
|
23天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
25天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构