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 关键字使用场景分析 | MESI 缓存一致性协议 | 总线嗅探机制 )