volatile关键字原理分析

本文详细探讨了多线程环境下volatile关键字的作用,包括为何多线程会导致线程不安全,内存屏障、JMM(Java内存模型)的概念,以及volatile如何确保可见性和防止重排序。同时,解释了内存屏障的使用,volatile的局限性,以及happens-before原则在并发编程中的重要性。
摘要由CSDN通过智能技术生成

为什么多线程会造成线程不安全

cpu缓存
由于磁盘io、内存io耗费时间比较多,为了提高cpu利用率,做了3级cpu缓存,L1、L2、L3
在这里插入图片描述

 - 这就会导致多核cpu中操作不同数据,对其它cpu中内容是不可见的
 - 比如cpu1从内存中读取了i=1,进行i+1操作
 - 当这个操作的内容还没有放到内存中时,cpu2读取的i还是等于1

于是cpu为了解决这个问题引入了总线锁和缓存锁
在这里插入图片描述

 - 总线锁:锁内存与cpu之间交互的过程,粗粒度锁,一旦锁住整个内存都被锁住
 - 缓存锁:只锁住cpu操作的这个缓存

缓存行
这里还要提一下缓存行的概念:缓存是一行一行的,操作系统每次取数据都是一段一段的去拿的,64位的操作系统,每次是64个字节,当去int i和j在一行时,那么每次加缓存锁都会讲i和j都锁住了,于是这里就出现了对齐补充,让每个变量都是自己一行从而解决缓存锁的问题

总线锁效率太低,而缓存锁,还是需要将缓存同步到内存中,于是诞生了缓存一致性协议(MESI)

  1. M(Modify[ˈmɒdɪfaɪ]) 表示共享数据只缓存在当前CPU缓存中,并且是被修改状态,也就是缓存的数据和主内存中的数据不一致
  2. E(Exclusive[ɪkˈskluːsɪv]) 表示缓存的独占状态,数据只缓存在当前CPU缓存中,并且没有被修改
  3. S(Shared[ʃerd]) 表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致
  4. I(Invalid[ˈɪnvəlɪd]) 表示缓存已经失效

为了执行这个协议于是创建了storebuffer,在完成修改后调用异步的storebuff去完成主内存的信息修改
为什么要是异步的,还是为了提高cpu的利用率,但是异步还是会产生不可见问题
在这里插入图片描述

重排序
重排序指的是从源代码到编译器重拍序,指令从排序到cpu重排序,来提高运行速度在这里插入图片描述

x=0;y=0;
a=0;b=0;
Thread t1=new Thread(()->{
	a=1;x=b;
	//重排序后x=b;a=1;
});
Thread t2=new Thread(()->{
	b=1;y=a;
	//y=a;b=1;
});
/**
* 可能的结果:
* 1和1
* 0和1
* 1和0
* ----
* 0和0
*/

内存屏障

为了解决这种不可见的安全问题,从硬件层面提供了内存屏障
相当于在操作的前后取消重排序,并且每次操作完都要立马写入内存,且读取的时候重新从内存中读取最新的值

JMM

jmm是软件层面的屏障通过封装后去调用硬件的屏障
有3种:loadload,storeload,fullload:全屏障

volatile是怎么解决可见性和重排序的

通过调用封装调用fullload,来完成内容的可见性
但是他只能解决可见性不能解决原子性
比如线程A和线程B同时读取了i的值为1,这时线程A修改i为1,线程B修改i为2还是会出现问题

可见性到底是什么,原子性呢

原子性:指的是操作不可分割,要么都成功要都失败
可见性:指的是操作对外可以看见,但是不能干预
有序性:程序执行的顺序按照代码的先后顺序执行

happensbefore原则

1.程序运行顺序规则
单一线程下,运行结果不能改变

2.volatile规则
对于 volatile 修饰的变量的写的操作, 一定 happen-before 后续对于 volatile 变量的读操作;

3.传递性规则,
如果 1 happens-before 2; 3happensbefore 4; 那么传递性规则表示: 1 happens-before 4;

4.join规则
如果线程 A 执行操作ThreadB.join()并成功返回,
那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回

5.start原则
如果线程 A 执行操作 ThreadB.start(),
那么线 程 A 的 ThreadB.start()操作 happens-before 线程 B 中 的任意操作

6.监视器锁的规则
对一个锁的解锁,happens-before 于 随后对这个锁的加锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
词法分析是编译器的第一个阶段,也称为扫描器。该阶段的任务是将源程序中的字符流转换为记号流,即将字符序列分解为有意义的单元,并生成相应的记号。 下面是一个简单的词法分析器的示例,可以用来识别C语言中的关键字和标识符。 ```c #include <stdio.h> #include <ctype.h> #include <string.h> int main() { char input[100]; printf("Enter C program:\n"); fgets(input, 100, stdin); // 读入一行输入 char *keywords[32] = {"auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while"}; int num_keywords = 32; char identifier[100]; int index = 0; for (int i = 0; i < strlen(input); i++) { // 跳过空格、制表符和换行符 if (isspace(input[i])) continue; // 如果是字母,可能是关键字或标识符 if (isalpha(input[i])) { identifier[index++] = input[i]; while (isalnum(input[++i])) identifier[index++] = input[i]; identifier[index] = '\0'; // 对比关键字 for (int j = 0; j < num_keywords; j++) { if (strcmp(identifier, keywords[j]) == 0) { printf("%s is a keyword\n", identifier); break; } } // 如果不是关键字,就是标识符 if (!isalpha(input[i])) { printf("%s is an identifier\n", identifier); index = 0; } i--; } } return 0; } ``` 该程序通过读取一行输入,逐个字符进行词法分析。它首先定义了所有C语言关键字的字符串数组,然后逐个读入字符并进行处理。如果读入的是空格、制表符或换行符,则跳过。如果读入的是字母,则可能是关键字或标识符,程序会一直读入字母和数字,直到遇到一个非字母非数字的字符。然后对比关键字数组,如果匹配上了,就输出该关键字;否则,就输出标识符。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值