volatile可以说是Java虚拟机提供的最轻量级的同步机制,Java内存模型对volatile专门定义了一些特殊的访问规则。

当一个变量定义为volatile之后,它将具备两种特性,第一是保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。 而普通变量不能做到这一点,普通变量的值在线程间传递均需要通过主内存来完成,例如,线程A修改一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写完成了之后再从主内存进行读取操作,新变量值才会对线程B可见。

关于volatile变量的可见性,需要注意的是volatile变量的运算必须是原子操作,演示例子如下:

/*** volatile变量自增运算测试* @author itmyhome**/public class VolatileTest {public static volatile int count = 0;public static void increase() {count++;}public static void main(String[] args) {Thread[] threads = new Thread[10];for (int i = 0; i < 10; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1)Thread.yield();System.out.println(count);}
}

这段代码发起了10个线程,每个线程对race变量进行10000次自增操作,如果这段代码能够正确并发的话,最后输出的结果应该是100000,但运行完这段代码之后,并不会获得期望的结果,而且会发现每次运行输出的结果都不一样,都是一个小于100000的数字。

问题出在count++之中,自增操作不具备原子性,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行

为达到预期效果可用如下的任何一种解决方法(代码略)

  • 1、使用synchronized
  • 2、使用Lock
  • 3、采用AtomicInteger

使用volatile变量的第二个语义是禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。 因为在一个线程的方法执行过程中无法感知到这点,这也就是Java内存模型中描述的所谓的“线程内表现为串行的语义”(Within-Thread As-If-Serial Semantics)。演示代码如下:

//线程1
boolean stop = false;
while(!stop){doSomething();
}//线程2
stop = true;

使用字段stop作为执行标识,但一定会执行doSomething()方法吗,答案是不一定。如果定义stop变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致线程2“stop = true”被提前执行,这样线程1中使用stop进行判断就可能出现错误,而volatile关键字则可以避免此类情况的发生。

下面列举实际操作运行的例子来分析volatile关键字是如何禁止指令重排序优化的,如下代码是一段标准的DCL单例代码,可以观察加入volatile和未加入volatile关键字时所生成汇编代码的差别

public class Singleton {private volatile static Singleton instance;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}public static void main(String[] args) {Singleton.getInstance();}
}

编译后,这段代码对instance变量赋值部分如下代码所示。

0x01a3de0f:mov$0x3375cdb0,%esi;……beb0cd75 33
;{oop('Singleton')}
0x01a3de14:mov%eax,0x150(%esi);……89865001 0000
0x01a3de1a:shr$0x9,%esi;……c1ee09
0x01a3de1d:movb$0x0,0x1104800(%esi);……c6860048 100100
0x01a3de24:lock addl$0x0,(%esp);……f0830424 00
;*putstatic instance
;-
Singleton:getInstance@24

通过对比就会发现,关键变化在于有volatile修饰的变量,赋值后(前面mov%eax,0x150(%esi)这句便是赋值操作)多执行了一个“lock addl $0x0,(%esp)”操作,这个操作相当于一个内存屏障(Memory Barrier或Memory Fence,指重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;但如果有两个或更多CPU访问同一块内存,且其中有一个在观测另一个,就需要内存屏障来保证一致性了。


参考文献:深入理解Java虚拟机 周志明 著

Java并发编程之volatile相关推荐

  1. Java并发编程之volatile关键字

    大概是因为项目.业务的原因,工作上几乎还没有使用过多线程相关的功能,相关知识差不多都忘了,所以最近补一下基础. volatile用来修饰共享变量,volatile变量具有 synchronized 的 ...

  2. java并发编程之Volatile详解

    前言 在Java中多个线程对公共变量的操作并不是直接在内存中操作的,每一个线程都会有一块自己的工作内存.线程会先从主内存中获取到变量的值到工作内存中进行修改在更新到主内存.假如有两个线程同时对某个变量 ...

  3. Java 并发编程之 volatile

    volatile变量是 Java 提供的另一种同步机制,保证线程之间的可见性,即一个线程对共享变量的修改能够立即被另一个线程看到. 使用也很简单,直接在变量前加 volatile 关键词: publi ...

  4. Java并发编程之volatile变量

    volatile提供了弱同步机制,用来确保将变量更新通知到其它线程.volatile变量不会被缓存在寄存器中或者对其它处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值.可以想象 ...

  5. zbb20180929 thread java并发编程之Condition

    java并发编程之Condition 引言 在java中,对于任意一个java对象,它都拥有一组定义在java.lang.Object上监视器方法,包括wait(),wait(long timeout ...

  6. java并发编程之4——Java锁分解锁分段技术

    转载自 java并发编程之4--Java锁分解锁分段技术 并发编程的所有问题,最后都转换成了,"有状态bean"的状态的同步与互斥修改问题.而最后提出的解决"有状态bea ...

  7. Java 并发编程之美:并发编程高级篇之一-chat

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

  8. Java 并发编程之美:并发编程高级篇之一

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

  9. Java并发编程之CAS第三篇-CAS的缺点

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

最新文章

  1. 物体可见性信息在3D检测中的探索CVPR2020(oral)
  2. 使用结构体数组统计男、女人数,计算全体学生的平均年龄、平均成绩,并将高于平均成绩的学生信息输出
  3. FPGA之道(68)原语的使用
  4. Android Animation实现元素在屏幕上按照指定轨迹运动,以及出现NullPointerException的解决方案
  5. nginx设置 二级域名 指定端口
  6. hive避免MR的情况
  7. 票据打印, 账单打印, 标签打印, 文档打印, 条码打印, 批量打印, 包装纸打印与设计,可变数据打印打印,发布,VC++源代码组件库解决方案...
  8. vue-webpack项目本地开发环境设置代理解决跨域问题
  9. android 获取程序名,Android_Android获取应用程序名称(ApplicationName)示例,MainActivity如下: 复制代码 代码 - phpStudy...
  10. python写日志到文件_python 通过logging写入日志到文件和控制台的实例
  11. 聊聊spring cloud的HystrixCircuitBreakerConfiguration
  12. 写在微信小程序一周年
  13. 【SAP Abap】记录一次完整的BDC录屏开发
  14. php仿淘宝课程设计任务书
  15. uniapp canvas绘图生成海报
  16. 中国造车要把百年车企按在地上打?你别说,我看有戏。
  17. Solved: ERROR: Failed building wheel for hdbscan
  18. 电脑表格日期怎么修改原有日期_如何在Excel表中自动生成记录数据的日期和时间...
  19. 烤仔观察 | 秋天的第一口“菠萝”真的那么好吃吗?
  20. 【Redis】错误:failed: Hostname must not be empty or null

热门文章

  1. Fleaphp 数组辅助文件中 array_to_tree 的bug修正
  2. 高级软件工程第九次作业:东理三剑客团队作业-随笔6
  3. MySQL复制表结构以及表数据
  4. 工业级嵌入式主板助力物联网行业发展
  5. Python编程的一些实例(1)
  6. ipv6联网几十分钟后显示无网络连接,v4网络正常的解决方法
  7. VS2022配置GDAL
  8. Inventor记录
  9. 一个不错的故事(上)
  10. Java很傻,但是IDE很聪明,Intellij IDEA 是一款好产品