我们要先从 CPU缓存模型 说起。
1. CPU缓存模型
为什么要弄一个CPU 高速缓存呢?
类似我们开发网站后台系统使用的缓存(比如redis)是为了解决程序处理速度和访问常规关系数据库速度不对等的问题。
CPU缓存 则是为了解决CPU 处理速度和内存处理速度不对等问题。
我们甚至可以把 内存看作外存的高速缓存 ,程序运行的时候,我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。
总结: CPU Cache 缓存的是内存数据用于解决CPU处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。
为了更好的理解,我画了简单的示意图如下(实际上,现代的CPU Cache通常分为三层,分别叫:L1、L2、L3 Cache):
CPU Cache 的工作方式:
先复制一份数据到CPU Cache
中,当CPU
需要用到的时候就可以直接从CPU Cache
中读取数据,当运算完成后,再将运算得到的数据写回到Main Memory
中。但是,这样存在内存缓存不一致性的问题 。比如我执行一个 i++
操作的话,如果两个线程同时执行的话,假设两个线程从CPU Cache
中读取的 i=1
,如果两个线程同时执行的话,假设两个线程同时执行的话,并且假设两个线程从CPU Cache
中读取的都是i=1
,两个线程做了1++
运算完之后再写会 Main Memory
之后 i=2
,而正确结果应该是 i=3
。
CPU 为了解决内存内存缓存不一致性问题可以通过制定缓存一致性协议或者其他手段来解决。
2. 讲一下 JMM(Java内存模型)
在当前的Java内存模型下,线程可以把变量保存 本地内存 (比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中变量值的拷贝,造成 数据的不一致。
要解决这个问题,就需要把变量声明为 volatile
,这就指示 JVM ,这个变量是共享且不稳定的,每次使用它都需要到主存中进行读取。
所以,volatile
关键字,除了防止 JVM 指令重排,还有一个重要的作用就是保证变量的可见性。
3. 并发编程的三个重要特性
1.原子性: 一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。要么全成功,要么全失败。synchronized
可以保证代码片段的原子性。
2.可见性: 当一个线程对共享变量做了修改,那么其他的线程都是立即可以看到修改后的最新值。volatile
关键字可以保证共享变量的可见性。
3.有序性: 代码在执行的过程中的先后顺序,Java在编译以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile
关键字可以禁止指令进行重排序。
4. 说说 sychronized 关键字 和 volatile 关键字的区别
sychronized
关键字 和 volatile
关键字是两个互补的存在,而不是对立的存在。
volatile
关键字是线程同步的轻量级实现,所以,volatile
性能肯定比sychronized
关键字的性能要好。但是,volatile
只能用于变量,而sychronized
可以修饰方法以及代码块。volatile
关键字能保证数据的可见性,但是不能保证数据的原子性。sychronized
两者都能保证。volatile
关键字主要用于解决变量在多个线程之间的可见性,而sychronized
解决的是多个线程之前访问资源的同步性。
5. 总结
本篇文章讲解了Java多线程进阶面试题-volatile关键字。代码和笔记由于纯手打,难免会有纰漏,如果发现错误的地方,请第一时间告诉我,这将是我进步的一个很重要的环节。以后会定期更新算法题目以及各种开发知识点,如果您觉得写得不错,不妨点个关注,谢谢。