众所周知,无限制下多线程操作共享变量是危险的,为了保证线程安全语义,一般的建议是在操作共享变量时加锁,比方说在用synchronized关键字修饰的方法内读写共享变量。

但是synchronized开销较大,有没有更轻量更优雅的解决方案呢?

volatile是轻量级的synchronized,在正确使用的前提下,它可以达到与synchronized一样的线程安全的语义,而且不会带来线程切换的开销。

volatile的作用是什么?

volatile保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小。

这句话可能难以理解,我来举个例子。

在我之前写的这篇文章<Ticket Lock, CLH Lock, MCS Lock>中,第一个给出的naive lock的例子里,flag变量被声明为volatile。

如果flag不是volatile而是普通变量,那会发生什么呢?

想象一个场景:线程A正在占有锁,线程B自旋监听flag变量。现在线程A退出临界区,将flag置为false。但是线程B能立即观察到flag的变化吗?

很不幸,不一定。

因为现代CPU中有多个core,每个core都有各自的高速缓存(cache),线程A对flag的修改只写在了cache上,需要一段不确定的时间才会被刷新到主存中。而线程B所在的core也会将flag变量缓存在cache中,这样就算主存中的flag变量发生了变化,线程B还是不一定能看到。

所以,如果flag是普通变量,naive lock是不成立的。

而如果将flag设置为volatile类型,JVM就会保证任何对flag变量的写操作会被立即刷新到主存里,同时还会让其他缓存了flag变量的core的对应的cache line失效,强迫其他线程必须从主存中读取最新的值。

这样就实现了共享变量被一个线程修改,其他线程立刻就能读到的语义。

那么volatile关键字的底层原理是什么呢?它是如何让写操作直接被刷新到主存,又是如何让其他core的cache line失效的呢?

如果观察编译出来的机器码,会发现在对volatile变量的写操作之后,会附加一条指令

lock addl $0x0,(%esp);

很容易可以看出,addl $0x0,(%esp) 这句话本身是没有任何作用的,效果与nop这样的空转指令等同,但是前面的lock前缀,有点意思。

查阅Intel的<Intel® 64 and IA-32 Architectures Software Developer’s Manual>,里面有这样一段话

8.1.4 Effects of a LOCK Operation on Internal Processor Caches

For the Intel486 and Pentium processors, the LOCK# signal is always asserted on the bus during a LOCK operation, even if the area of memory being locked is cached in the processor.

For the P6 and more recent processor families, if the area of memory being locked during a LOCK operation is cached in the processor that is performing the LOCK operation as write-back memory and is completely contained in a cache line, the processor may not assert the LOCK# signal on the bus. Instead, it will modify the memory location internally and allow it’s cache coherency mechanism to ensure that the operation is carried out atomically. This operation is called “cache locking.” The cache coherency mechanism automatically prevents two or more processors that have cached the same area of memory from simultaneously modifying data in that area.

大概意思是说,遇到lock指令,对应的core上的cache line会被强制刷回到主存中。然后由于缓存一致性协议的效果(参见我的这篇博客<缓存一致性协议>),其他core上的对应的cache line也会被强制设置为invalid。

于是volatile的语义就实现了,仅需这一条lock指令。

但是volatile能保证原子性吗?比方说如果我们多线程对一个volatile型的变量做自增操作,这是线程安全的吗?
答曰:并不是。

我们想象有两个线程对volatile类型的变量count做自增操作,count初始值为0,两个线程同时拿到了0,同时自增为1,然后同时写回,这样count的结果还是1,不符合期望。

那么我们应该怎么办呢?

参考AtomInteger,使用UNSAFE提供的CAS操作更新count,在上面的场景中,两个线程同时执行CAS(count, 0, 1),只有其中一个线程能执行成功,另外一个线程失败重试,读取新的count值,然后执行CAS(count, 1, 2),这一次执行成功了,那么count的最终值是2。符合期望。

参考资料:

聊聊并发(一)深入分析Volatile的实现原理

深入理解volatile关键字

Intel® 64 and IA-32 Architectures Software Developer’s Manual,8.1.4节,p257

解析Java的volatile关键字的更多相关文章

  1. 解析java中volatile关键字

    在Java多线程编程中经常volatile,有时候这个关键字和synchronized 或者lock经常有人混淆,具体解析如下:  在多线程的环境中会存在成员变量可见性问题: java的每个线程都存在 ...

  2. 深入解析Java中volatile关键字的作用

    转(http://m.jb51.net/article/41185.htm)Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制 在java线 ...

  3. 【转】java中volatile关键字的含义

    java中volatile关键字的含义   在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  4. 转:java中volatile关键字的含义

    转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  5. Java中Volatile关键字详解 (转自郑州的文武)

    java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 一.基本概念 先补充一下概念:J ...

  6. Java中volatile关键字解析

    一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存 ...

  7. java复习volatile关键字解析

    转自https://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受 ...

  8. 关于java的volatile关键字与线程栈的内容以及单例的DCL

    用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值.volatile很容易被误用,用来进行原子性操作. package com.guangshan.test; pub ...

  9. Java 并发 —— volatile 关键字

    volatile 修饰变量等于向编译器传达如下两层含义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 禁止进行指令重排序. volat ...

随机推荐

  1. centos使用--防火墙

    目录 1 切换到zsh 1.1 查看系统当前的shell 1.2 查看bin下是否有zsh包 1.3 安装zsh包 1.4 切换shell至zsh 2 安装oh-my-zsh 2.1 oh-my-zs ...

  2. 网易考拉Android客户端网络模块设计

    本文来自网易云社区 作者:王鲁才 客户端开发中不可避免的需要接触到访问网络的需求,如何把访问网络模块设计的更具有扩展性是每一个移动开发者不得不面对的事情.现在有很多主流的网络请求处理框架,如Squar ...

  3. PowerDesigner如何将一个包里的表拷贝到另一个表以后在视图中也可以显示?

    第一步:选中PhysicalDiagram_1 第二步:点击:“ Symbol”按钮, 第三步:点击“Show Symbols”,出现如下界面 第四步:选中State.  

  4. javascript检测数组

    在ECMAScript5中的数组已经引入了isArray方法,该方法的目的就是检测变量是否为数组. 但是对于ie6.7等古老的浏览器是没有这样的方法的,在Zakas写的一本书上摘到一个函数,基本能优雅 ...

  5. Java进行身份证格式强校验(准)

    最近做了一个系统,涉及到对用户输入的身份证号进行校验,减少脏数据传入后台处理并降低企业验证成本,因此在接入层便对输入信息做格式强校验. 直接附上代码,可直接使用. package hope.ident ...

  6. 【bzoj1195】[HNOI2006]最短母串 AC自动机+状态压缩+BFS最短路

    原文地址:http://www.cnblogs.com/GXZlegend/p/6825226.html 题目描述 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串 ...

  7. POJ 3177 Redundant Paths 无向图边双联通基础题

    题意: 给一个无向图,保证任意两个点之间有两条完全不相同的路径 求至少加多少边才能实现 题解: 得先学会一波tarjan无向图 桥的定义是:删除这条边之后该图不联通 一条无向边(u,v)是桥,当且仅当 ...

  8. java中的Timer

    一个java中用Timer做的简单定时器小程序. package com.test.lx; import java.util.TimerTask; public class TimeTaskTest ...

  9. freemarker的简单入门程序

    本文主要介绍了freemarker的常用标签<#list>   <#import>  <#assign>  <#if> <#else> &l ...

  10. 在ESXi使用esxcli命令強制关闭VM

    最近學到一個在VMware ESXi 下面強制關閉一個沒有反應的VM的方法, 一般正常都是使用vSphere Client 去控制VM電源, 但是有時會發生即使用裡面的Power Off 按鈕但是還是 ...