Java并发(四):volatile的实现原理
2018-11-02 08:49:38来源:博客园 阅读 ()
synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronized
volatile是一个变量修饰符,只能用来修饰变量。
volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
volatile读:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
volatile实现原理
1)JMM把内存屏障指令分为下列四类:

StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)。
Store:数据对其他处理器可见(即:刷新到内存)
Load:让缓存中的数据失效,重新从主内存加载数据
2)JMM针对编译器制定的volatile重排序规则表
| 是否能重排序 | 第二个操作 | ||
| 第一个操作 | 普通读/写 | volatile读 | volatile写 |
| 普通读/写 | NO | ||
| volatile读 | NO | NO | NO |
| volatile写 | NO | NO | |
举例来说,第三行最后一个单元格的意思是:在程序顺序中,当第一个操作为普通变量的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作。
从上表我们可以看出:
- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
JMM内存屏障插入策略(编译器可以根据具体情况省略不必要的屏障):
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 对于这样的语句Store1;
StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- 对于这样的语句Store1;
- 在每个volatile写操作的后面插入一个StoreLoad屏障。
- 对于这样的语句Store1;
StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
- 对于这样的语句Store1;
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 对于这样的语句Load1;
LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- 对于这样的语句Load1;
- 在每个volatile读操作的后面插入一个LoadStore屏障。
- 对于这样的语句Load1;
LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- 对于这样的语句Load1;
volatile保证可见性
volatile修饰的变量写之后将本地内存刷新到主内存,保证了可见性
volatile保证有序性
volatile变量读写前后插入内存屏障以避免重排序,保证了有序性
volatile不保证原子性
volatile不是锁,与原子性无关
要我说,由于CPU按照时间片来进行线程调度的,只要是包含多个步骤的操作的执行,天然就是无法保证原子性的。因为这种线程执行,又不像数据库一样可以回滚。如果一个线程要执行的步骤有5步,执行完3步就失去了CPU了,失去后就可能再也不会被调度,这怎么可能保证原子性呢。
为什么synchronized可以保证原子性 ,因为被synchronized修饰的代码片段,在进入之前加了锁,只要他没执行完,其他线程是无法获得锁执行这段代码片段的,就可以保证他内部的代码可以全部被执行。进而保证原子性。(摘自http://www.hollischuang.com/archives/2673)
volatile不保证原子性的例子:
/** * 创建10个线程,然后分别执行1000次i++操作。目的是程序输出结果10000 * 但是,多次执行的结果都小于10000。这其实就是volatile无法满足原子性的原因。 */ public class Test { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for (int i = 0; i < 10; i++) { new Thread() { public void run() { for (int j = 0; j < 1000; j++) test.increase(); }; }.start(); } while (Thread.activeCount() > 1) // 保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }
参考资料
深入理解Java中的volatile关键字
Java并发(一):Java内存模型干货总结
【死磕Java并发】—–深入分析volatile的实现原理
再有人问你volatile是什么,把这篇文章也发给他。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:项目应该如何分层(转载)
下一篇:Hibernate缓存
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash
