`

Volatile的正确使用

    博客分类:
  • java
阅读更多
以前就看到过Volatile关键字, 只知道跟多线程同步有关, 但一直没去过问具体的含义。
今天想了起来, 查找了一下Volatile相关资料。
就理解而言,看这篇文章就足够了:
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

以下我还是做一下自问自答,以示学习和理解的过程。

一、什么是Volatile?
引用

Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。
Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形。

这里说volatile 是轻量的同步,又跟同步方法和同步块放在一起,说是同步机制。其实在看过之后的内容后,我倒觉得即使volatile是同步机制的一种,但也不合适将volatile 跟synchronized立刻扯到一起来介绍,因为很容易让人以为volatile跟synchronized用的一样的锁方法实现的,但实际上它又不全是。

再看这个就会有个认识:
引用

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

volatile 实际上只保证了可见性跟synchronized相同,但又不会有互斥。Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。

看到这里, 你一定会疑问volatile什么时候用?怎样用?
引用

什么时候用到volatile?
如果读操作远远大于写操作,出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile 变量而不是锁。当使用 volatile 变量而非锁时,某些习惯用法(idiom)更加易于编码和阅读。此外,volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题,volatile 变量还提供优于锁的性能优势。


引用

正确的使用volatile:
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
    * 对变量的写操作不依赖于当前值。
    * 该变量没有包含在具有其他变量的不变式中。
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。


下面可以看看使用volatile 的典型场景,可以帮助理解:
引用

volatile boolean shutdownRequested;

...

public void shutdown() { shutdownRequested = true; }

public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

很可能会从循环外部调用 shutdown() 方法 —— 即在另一个线程中 —— 因此,需要执行某种同步来确保正确实现 shutdownRequested 变量的可见性。(可能会从 JMX 侦听程序、GUI 事件线程中的操作侦听程序、通过 RMI 、通过一个 Web 服务等调用)。然而,使用 synchronized 块编写循环要比以上所示的 volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。


看看volatile的性能:
引用

使用 volatile 变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用相应的锁简单得多。使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁。

很难做出准确、全面的评价,例如 “X 总是比 Y 快”,尤其是对 JVM 内在的操作而言。(例如,某些情况下 VM 也许能够完全删除锁机制,这使得我们难以抽象地比较 volatile 和 synchronized 的开销。)就是说,在目前大多数的处理器架构上,volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样。而 volatile 写操作的开销要比非 volatile 写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。

volatile 操作不会像锁一样造成阻塞,因此,在能够安全使用 volatile 的情况下,volatile 可以提供一些优于锁的可伸缩特性。如果读操作的次数要远远超过写操作,与锁相比,volatile 变量通常能够减少同步的性能开销。


再看看一个相对开销较低的读-写锁策略
引用

@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this") private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {
        return value++;
    }
}

之所以将这种技术称之为 “开销较低的读-写锁” 是因为您使用了不同的同步机制进行读写操作。因为本例中的写操作违反了使用 volatile 的第一个条件,因此不能使用 volatile 安全地实现计数器 —— 您必须使用锁。然而,您可以在读操作中使用 volatile 确保当前值的可见性,因此可以使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。然而,要随时牢记这种模式的弱点:如果超越了该模式的最基本应用,结合这两个竞争的同步机制将变得非常困难。



总结:
与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。
Volatile相比synchronized,并没有保证原子性操作,因此不能用在需要加锁的环境,但可以跟synchronized结合,实现开销较低的读写锁。
使用Volatile,需要严格遵循 volatile 的使用条件:
1)即变量真正独立于其他变量和自己以前的值
2)该变量没有包含在具有其他变量的不变式中
因此volatile 事实上的应用场景常常应该是变量和边界检查,但又要注意以上两个规则。
某些情况下,需要保证原子性操作,还是得用到lock。
需要记住,使用 volatile 的代码往往比使用synchronized的代码更加容易出错,不能单为了更好的性能、却忽略了本来应该保证的安全性。
最好结合这篇讨论一起看:
http://www.iteye.com/topic/109150?page=1
分享到:
评论

相关推荐

    stm32 volatile变量的正确使用

    volatile变量的使用是区分c程序员和嵌入式系统程序员的最基本问题。不懂得volatile变量的内容将带来灾难。这个文档帮助你减少因此带来的bug。

    Volatile的正确使用1

    简介:Java语言包含两种内在的同步机制:同步块(或方法)和volatile变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同

    volatile的用法

    volatile的用法,在写代码正确使用volatile,正确理解volatile的用法,增强代码的健壮性

    Java 理论与实践: 正确使用 volatile 变量 线程同步

     使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。  由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此...

    详解java如何正确使用volatile

    给大家分享了java如何正确使用volatile的相关知识点内容,有兴趣的朋友可以参考学习下。

    Java 关键字 volatile 的理解与正确使用

    本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方,感兴趣的朋友一起看看吧

    java入门教程:数据类型_Java理论与实践如何正确使用Volatile变量.docx

    java入门教程:数据类型_Java理论与实践如何正确使用Volatile变量.docx

    深入分析Volatile的实现原理

    本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的,通过深入分析能帮助我们正确的使用Volatile变量。

    何为C语言关键字volatile

    作者:王姗姗,华清远见嵌入式学院讲师。... 假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。  下面我们来看下宝典中提

    并发编程基础知识,java内存模型及多线程、volatile

    然会对程序的正确性造成破坏。因此,我们需要在深⼊了解并⾏机制的前提下,再定义⼀种规则, 来保证多个线程间可以有效地、正确地协同⼯作。⽽JMM就是为此⽽⽣的。 ● JMM的关键技术点都是围绕着多线程的原⼦性、可...

    Java关键字volatile和synchronized作用和区别

    主要为大家详细介绍了Java关键字volatile和synchronized的作用和区别,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    Java里volatile关键字是什么意思

    volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。这篇文章主要介绍了Java里volatile关键字是什么意思的相关资料,需要的朋友可以参考下

    Java并发编程(18)第五篇中volatile意外问题的

    Java并发编程(18)第五篇中volatile意外问题的正确分析解答(含代码)编程开发技术共5页.pdf.zip

    java中volatile和synchronized的区别与联系

    主要介绍了java中volatile和synchronized的区别与联系的相关资料,希望通过本文能帮助到大家,让大家理解这部分内容,需要的朋友可以参考下

    解开Volatile的面纱V1.1

    现是一件很棘手且难以解决的事情,为了尽可能的减少并发问题的产生,正确的编写并发程序显得尤其 重要。 解决并发问题,我们一般需要从原子性、可见性和有序性三方面入手,借助Java关键字及各种同 步工具类来实现。 ...

    EDA/PLD中的何为C语言关键字volatile

    在学习C关键词的时候,我们看到了一个新面孔——... 假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。  下面我们来看下宝典中

    一些Java中不为人知的特殊方法.docx

    他们发现,使用immutator(这个项目探索了Java的一些不为人知的细节)生成的Java代码使用volatile了作为方法的关键字,而这样的代码没法通过编译。结果就是这根本没法用。 这是怎么回事?syntethic和bridge方法又是...

    多线程并发编程在Netty中的应用分析.zip

    大致内容包括: Java内存模型 Java内存交互协议 Java的线程 Netty的并发编程分析 正确的使用锁 volatile的正确使用 CAS指令和原子类 线程安全类 读写锁的应用

    深入理解Java源码:提升技术功底,深度掌握技术框架,快速定位线上问题

    并发优化包括volatile的大量、正确使用,CAS和原子类的广泛使用,线程安全容器的使用,以及通过读写锁提升并发性能等。 总的来说,Java源码的学习和理解是提升技术功底,深度掌握技术框架,快速定位

    Java理论与实践:修复Java内存模型2

    JSR 133显著增强了volatile的语义,这样就可以可靠地使用volatile标志表明程序状态被另一个线程改变了。JSR 133还显著地增强了final的语义。极大地加强了并发程序中不变对象的效用,不变对象最终成为固有的线程安全...

Global site tag (gtag.js) - Google Analytics