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. 液晶12864COG 液晶模块串口/并口ST7565R带背3.3v 12864-14显示屏
  2. Java取得当前类的路径
  3. 数据可视化01--笔记
  4. [翻译]Triggerless design.md
  5. 李涓子 | 机器智能加速器:大数据环境下知识工程的机遇和挑战
  6. 精通webpack的5大关键点
  7. python 复制文件并重命名_好书推荐 | Python 如此神奇,让繁琐工作自动化
  8. nusoap传递数组对象
  9. 同样可以建站,云服务器和虚拟主机的区别在哪?
  10. 异常值(outlier)
  11. Windows Server 2008 优化
  12. Rsyslog的模板template详解
  13. Java:关于负数的向上转型
  14. 高等数学(第七版)同济大学 习题7-8 个人解答
  15. 使用 busybox 为 龙芯2f 创建 文件系统
  16. 面试题:fail-safe 机制与 fail-fast 机制分别有什 么作用
  17. 测绘程序设计——度分秒与弧度制转化(C#)
  18. Python_Task02:条件、循环结构
  19. 七个经典推理(据说没有人能全部答对)
  20. Leads、SDR、MQL、SQL、L2C术语概念,线索创建与清洗、线索分级模型、线索流转策略、线索的统计与分析

热门文章

  1. pacemaker+drbd主从
  2. Weblogic常见错误以及解决办法
  3. 环视拼接-鱼眼镜头模型
  4. freemarker字符串替换操作
  5. 支持国产ARM64架构部署,支持使用rz、sz命令上传下载文件,JumpServer堡垒机v2.12.0发布
  6. golang || gin运行,出现类似这种的错误: missing go.sum entry; to add it:
  7. 有效使用二维码进行APP推广
  8. 2020平安科技校招内推
  9. Python实现 鱼群算法库
  10. mysql 执行顺序 SQL语句执行顺序分析