JUC整理笔记二之聊聊volatile
要想学好JUC,还得先了解 volatile 这个关键字。了解 volatile ,我们从一个例子开始吧。
本文不会很详细去说java内存模型,只是很简单地学习一下volatile
一个例子
package jfound.demo;
import java.util.concurrent.TimeUnit;
public class TaskRunner {
private static boolean ready = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (ready) {
}
}).start();
TimeUnit.SECONDS.sleep(1);
ready = false;
}
}
这个程序里面,新开一个线程,ready
初始化值为true
, 线程里面是一个死循环,当 ready
修改为 false
的时候,我们希望线程里面的死循环会结束,然后jvm会停止。
然后在这个例子里面,程序根本不会停止。但当 ready
被 volatile
关键字修饰的时候,程序符合我们预期,停止了。
....
private static volatile boolean ready = true;
...
CPU执行及缓存
CPU负责执行程序指令,但是他们需要从内存(RAM)中获取程序指令和所需要的数据。由于CPU每秒能执行大量的执行,如果每执行一次指令就从内存(RAM)中获取数据的话,显然是不够理想的,毕竟CPU与内存之间还是有一定的距离的。为了改善这种情况,CPU会有一系列的优化,例如指令重排序,当然,还有缓存。下图为CPU及内存层次的结构。
当CPU获取指令的时候,也会把指令所需要的数据读进CPU缓存中,当在某些时刻,通常是指令改变或者缓存失效时,CPU会重新从内存(RAM)中读取指令或数据。
在上面的例子中,新开的线程在做循环的时候,会读取 ready
变量到该线程所执行的CPU缓存中,当 main
线程修改 ready
变量为 false
的时候,是首先写在 main
线程所执行的CPU的缓存中,在某些时刻才会写入到内存(RAM)中。也就是说要让新开的线程停止的话,必须是 main
线程修改的变量写入到内存(RAM)中,而且新开的线程的所在的CPU缓存要失效,让其重新读取 ready
变量。然而,没有加 volatile
之前,main
线程并不会实时把变量 ready
写入到内存(RAM)中去,新开的线程也不会从内存中获取 ready
新的数据。
缓存一致性协议(MESI协议)
上述的问题就是大名鼎鼎的缓存不一致性的问题,也就是在并发编程中所要解决的主要问题之一。
在早期的CPU中,是通过在总线(上图中的Bus)上加LOCK#的形式来解决缓存不一致的问题,当加上总线锁的时候,加锁的CPU就独占内存,其他CPU就不能读取内存,也就是不能执行指令,只能乖乖等待锁释放,这样的总线锁效率很低,不过是能解决了缓存不一致的问题。
为了提高效率,就出现了缓存一致性协议。缓存一致性是为了保证每个缓存中使用的共享变量的副本是一致的,它的核心思想是:当CPU写数据时,如果发现该操作的变量是共享变量,即使在其他CPU中也存在该变量的副本,会发出通知,让其他CPU该变量的缓存行置为无效状态,因此即使其他CPU需要读取这个变量时,发现自己缓存中的该变量的缓存行无效了,那么就会从内存中重新读取。
MESI全名是Modified、Exclusive、 Share or Invalid,使每一个缓存行可能处于M、E、S和I这四种状态之一,
- M:被修改的。处于这一状态的数据,只在本CPU中有缓存数据,而其他CPU中没有。同时其状态相对于内存中的值来说,是已经被修改的,且没有更新到内存中。
- E:独占的。处于这一状态的数据,只有在本CPU中有缓存,且其数据没有修改,即与内存中一致。
- S:共享的。处于这一状态的数据在多个CPU中都有缓存,且与内存一致。
- I:无效的。本CPU中的这份缓存已经无效。
例子解析
volatile
关键字有着上面所说的触发缓存一致性的功能,所以在加上 volatile
关键字之后,main
线程把 ready
修改为 false
的时候,新开的线程是可以读取到修改后的 ready
的值,所以程序是可以符合我们的预期,停止了。
总结
本文通过上面的一个小例子来解析了 volatile
的一个功能,缓存一致性,为接下来学习 JUC 做准备。当然 volatile
关键字在java中还会有其他的功能,例如 happer-before、内存屏障、重排序等等,这些就不在本文赘述了。
微信关注我,发现更多java领域知识
最新文章
- js继承相关
- 学习笔记——关于HTML(含HTML5)的块级元素和行级(内联)元素总结
- SortedSet有序集合类型
- [ios][opengles]opengles在ios上的透明问题
- 最小化安装centos的init初始化脚本
- memcache和memcahced的区别
- Memcached 内存级缓存
- Jetty使用
- Niagara AX之BajaScript资料哪里找
- HTC与英特尔联手打造无线VR解决方案
- MFC实现登录对话框连接access数据库方式
- 构建自动化前端样式回归测试——BackstopJS篇
- 1.如何安装ubuntu
- No Transport ,Network中看不到ajax请求
- 浏览器关闭,onunload和onbeforeunload的使用
- file上传图片,base64转换、压缩图片、预览图片、将图片旋转到正确的角度
- linux svn 多项目设置
- MyEclipse配置默认自带的HTML/JSP代码格式化
- scrapy爬取动态分页内容
- 028、HTML 标签1列表、图片、表格
热门文章
- CSS开发技巧(四):解决flex多行布局的行间距异常、子元素高度拉伸问题
- CCF NOI1039 2的n次方
- 从实践出发:微服务布道师告诉你Spring Cloud与Boot他如何选择
- NLP入门之语音模型原理
- nginx平滑升级、在线添加模块(tengine 动态加载模块)
- POJ 1845-Sumdiv(厉害了这个题)
- P1465 序言页码 Preface Numbering (手推)
- 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)
- PHP循环引用会遇到的坑
- 折腾了一晚上的“equals”和“==”