简述

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,当一个变量定义为volatile,它具有内存可见性以及禁止指令重排序两大特性,为了更好地了解volatile关键字,我们可以先看Java内存模型

Java内存模型

Java内存模型规定了所有的变量都存储在主内存中,每条线程拥有自己的工作内存,工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读写)都必须在工作内存中进行,不同的线程之间无法直接访问对方工作内存的变量。线程、主内存、工作内存关系:

以经典的i++为例,线程A从主内存获取变量i值放入到工作内存的变量副本,然后在工作内存中将i+1,最后将新值同步到主内存中。从中我们可以看出简单的i++,分了3个步骤,可以明显发现在线程A从主内存获取i值步骤后,可能有其他线程同步主内存中变量i的值,当线程A想要将i+1结果同步到主内存时就会出现不正确的结果,这是典型的线程不安全。

volatile特性

  • 可见性

当一个线程修改了共享变量,其他线程能够立即得知这个修改。Java内存模型通过在变量修改后将新值同步回主内存,volatile变量能保证新值能立即同步到主内存,以及每次使用前立即从主内存刷新(synchronized和final两个关键字也具备)。还是拿i++为例,volatile修饰的i可以确保,从主存中所获取的变量i一定是最新的。

  • 有序性

禁止指令重排序,程序执行的顺序按照代码的先后顺序执行。
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。
①.编译器重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
②.处理器重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序

从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

1属于编译器重排序,2和3属于处理器重排序。

volatile使用场景

在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:
①.运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
②.变量不需要与其他的状态变量共同参与不变约束

两个使用场景:

  • 状态标记

使用volatile变量来控制并发,当shutdown()方法被调用时,能保证所有线程中执行的doWork()方法都立即停下来


public class VolatileTest {
private volatile boolean shutdownRequested;
   public void shutdown() {
shutdownRequested = true;
} public void doWork(){
while (!shutdownRequested) {
// 业务逻辑
}
}
}
复制代码
复制代码

复制代码

  • DCL(双锁检测)

单例模式的一种实现方式

public class Singleton {

    private volatile static Singleton singleton;

    public static Singleton getInstance() {
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
复制代码
复制代码

复制代码

volatile实现

从硬件架构上来讲,处理器使用写缓冲区来临时保存向内存写入的数据,可以减少对内存总线的占用。虽然写缓冲区有这么多好处,但此操作仅对它所在的处理器可见,这个特性会对内存操作的执行顺序产生重要的影响,由于操作缓冲区是异步操作所以在外面看来,先写后读,还是先读后写,没有严格的固定顺序。

volatile修饰的变量相对于普通变量会多出一个lock前缀指令,这个操作相当于一个内存屏障(只有一个CPU访问内存时,不需要内存屏障;但如果有两个或更多CPU访问同一块内存,且其中有一个在观测另一个,就需要内存屏障来保证一致性)。

是否能重排序 第二个操作
第一个操作 普通读 普通写 volatile读 volatile写
普通读       LoadStore
普通写       StoreStore
volatile读 LoadLoad LoadStore LoadLoad LoadStore
volatile写     StoreLoad StoreStore

空白的单元格代表在不违反Java的基本语义下的重排是允许的。
StoreStore屏障:保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存
StoreLoad屏障:避免volatile写与后面可能有的volatile读/写操作重排序
LoadLoad屏障:禁止处理器吧上面的volatile读与下面的普通读中排序
LoadStore屏障:禁止处理器把上面的volatile读与下面的普通写重排序

示例:


public class VolatileTest {
int a = 0;
volatile int var1 = 1;
volatile int var2 = 2;
void readAndWrite() {
int i = var1; //volatile读
int j = var2; //volatile读
a = i + i; //普通读
var1 = i + 1; //volatile写
var2 = j * 2; //volatile写
}
复制代码
复制代码

} 复制代码

大致过程:

感谢

1.《深入理解Java虚拟机》
2.占小狼——面试必问的volatile,你了解多少?
3.《Java并发编程的艺术》

最新文章

  1. FusionChart 水印破解方法(代码版)
  2. winform 使用 ReportViewer做报表
  3. JPA persistence
  4. [Google Guava]学习--新集合类型Multimap
  5. mysql事务,SET AUTOCOMMIT,START TRANSACTION
  6. 修改更新源sources.list,提高软件下载安装速度(提供Kali 2.0 更新源)
  7. #!/bin/bash
  8. HttpClient——连接关闭
  9. Drupal 7.31 SQL注入漏洞利用具体解释及EXP
  10. DDL_数据库模式定义语言
  11. response返回随笔
  12. git拉取远程分支并创建本地分支和Git中从远程的分支获取最新的版本到本地
  13. 在Fedora 20下编译安装QEMU
  14. nginx错误记录
  15. RobotFramework自动化测试框架的基础关键字(四)
  16. idea安装proto插件后不能识别.proto文件解决方案
  17. 日常工作中 Oracle12c参数的处理:
  18. django在读取数据库时未筛选到符合条件的记录会报错
  19. Linux笔记 #08# shell编程从零开始到低配学生管理系统
  20. CodeForces 459C Pashmak and Buses(构造)题解

热门文章

  1. 最多水容器(M)
  2. LintCode-70.二叉树的层次遍历 II
  3. Zigbee安全基础篇Part.2
  4. python: error while loading shared libraries: libpython2.7.so.1.0: cannot open shared object file: No such file or directory
  5. dedecms给原模型添加新字段
  6. winform全局异常捕获
  7. 【题解】NOIP2015推销员
  8. [HNOI2009]有趣的数列 卡特兰数
  9. BZOJ5334:[TJOI2018]数学计算——题解
  10. BZOJ1834:[ZJOI2010]网络扩容——题解