原文:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html?S_TACT=105AGX52&S_CMP=techcsdn
Java 理论与实践: 正确使用 Volatile 变量volatile 变量使用指南 |
级别: 中级 Brian Goetz (brian.goetz@sun.com), 高级工程师, Sun Microsystems 2007 年 7 月 05 日 Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。在这期的 Java 理论与实践 中,Brian Goetz 将介绍几种正确使用 volatile 变量的模式,并针对其适用性限制提出一些建议。<!----><!----><!----> Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。 Volatile 变量具有 出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile 变量而不是锁。当使用 volatile 变量而非锁时,某些习惯用法(idiom)更加易于编码和阅读。此外,volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。 您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。 第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作( 大多数编程情形都会与这两个条件的其中之一冲突,使得 volatile 变量不能像 清单 1. 非线程安全的数值范围类
这种方式限制了范围的状态变量,因此将 使用 volatile 变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用相应的锁简单得多。使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁。 很难做出准确、全面的评价,例如 “X 总是比 Y 快”,尤其是对 JVM 内在的操作而言。(例如,某些情况下 VM 也许能够完全删除锁机制,这使得我们难以抽象地比较 volatile 操作不会像锁一样造成阻塞,因此,在能够安全使用 volatile 的情况下,volatile 可以提供一些优于锁的可伸缩特性。如果读操作的次数要远远超过写操作,与锁相比,volatile 变量通常能够减少同步的性能开销。 很多并发性专家事实上往往引导用户远离 volatile 变量,因为使用它们要比使用锁更加容易出错。然而,如果谨慎地遵循一些良好定义的模式,就能够在很多场合内安全地使用 volatile 变量。要始终牢记使用 volatile 的限制 —— 只有在状态真正独立于程序内其他内容时才能使用 volatile —— 这条规则能够避免将这些模式扩展到不安全的用例。 也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。 很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”,如清单 2 所示: 清单 2. 将 volatile 变量作为状态标志使用
很可能会从循环外部调用 这种类型的状态标记的一个公共特性是:通常只有一种状态转换; 模式 #2:一次性安全发布(one-time safe publication) 缺乏同步会导致无法实现可见性,这使得确定何时写入对象引用而不是原语值变得更加困难。在缺乏同步的情况下,可能会遇到某个对象引用的更新值(由另一个线程写入)和该对象状态的旧值同时存在。(这就是造成著名的双重检查锁定(double-checked-locking)问题的根源,其中对象引用在没有同步的情况下进行读操作,产生的问题是您可能会看到一个更新的引用,但是仍然会通过该引用看到不完全构造的对象)。 实现安全发布对象的一种技术就是将对象引用定义为 volatile 类型。清单 3 展示了一个示例,其中后台线程在启动阶段从数据库加载一些数据。其他代码在能够利用这些数据时,在使用之前将检查这些数据是否曾经发布过。 清单 3. 将 volatile 变量用于一次性安全发布
如果 该模式的一个必要条件是:被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)。volatile 类型的引用可以确保对象的发布形式的可见性,但是如果对象的状态在发布后将发生更改,那么就需要额外的同步。 模式 #3:独立观察(independent observation) 安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。 使用该模式的另一种应用程序就是收集程序的统计信息。清单 4 展示了身份验证机制如何记忆最近一次登录的用户的名字。将反复使用 清单 4. 将 volatile 变量用于多个独立观察结果的发布
该模式是前面模式的扩展;将某个值发布以在程序内的其他地方使用,但是与一次性事件的发布不同,这是一系列独立事件。这个模式要求被发布的值是有效不可变的 —— 即值的状态在发布后不会更改。使用该值的代码需要清楚该值可能随时发生变化。 volatile bean 模式适用于将 JavaBeans 作为“荣誉结构”使用的框架。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 清单 5. 遵守 volatile bean 模式的 Person 对象
前面几节介绍的模式涵盖了大部分的基本用例,在这些模式中使用 volatile 非常有用并且简单。这一节将介绍一种更加高级的模式,在该模式中,volatile 将提供性能或可伸缩性优势。 volatile 应用的的高级模式非常脆弱。因此,必须对假设的条件仔细证明,并且这些模式被严格地封装了起来,因为即使非常小的更改也会损坏您的代码!同样,使用更高级的 volatile 用例的原因是它能够提升性能,确保在开始应用高级模式之前,真正确定需要实现这种性能获益。需要对这些模式进行权衡,放弃可读性或可维护性来换取可能的性能收益 —— 如果您不需要提升性能(或者不能够通过一个严格的测试程序证明您需要它),那么这很可能是一次糟糕的交易,因为您很可能会得不偿失,换来的东西要比放弃的东西价值更低。 目前为止,您应该了解了 volatile 的功能还不足以实现计数器。因为 然而,如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。清单 6 中显示的线程安全的计数器使用 清单 6. 结合使用 volatile 和 synchronized 实现 “开销较低的读-写锁”
之所以将这种技术称之为 “开销较低的读-写锁” 是因为您使用了不同的同步机制进行读写操作。因为本例中的写操作违反了使用 volatile 的第一个条件,因此不能使用 volatile 安全地实现计数器 —— 您必须使用锁。然而,您可以在读操作中使用 volatile 确保当前值的可见性,因此可以使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。然而,要随时牢记这种模式的弱点:如果超越了该模式的最基本应用,结合这两个竞争的同步机制将变得非常困难。 与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 |
发表评论
-
集群的可扩展性及其分布式体系结构(2)
2007-09-21 16:13 1631... -
集群的可扩展性及其分布式体系结构(2)-上
2007-09-21 16:11 1242... -
转 集群的可扩展性及其分布式体系结构(1)
2007-09-21 16:09 1206... -
网格计算 —— 下一代分布式计算
2007-09-21 16:01 1466... -
负载均衡
2007-09-21 15:09 1127实 ... -
转 Java语言的特点
2007-09-12 11:51 1792... -
转载 谈谈Unicode编码
2007-09-12 11:49 945这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地 ... -
转载 字符串编码(charset, encoding/decoding)问题原理
2007-09-12 11:47 1132转载:http://www.iteye.com/article ...
相关推荐
Java语言规范中指出:为了获得佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。 这样当多个线程同时与某个对象交互时,必须要注意到要让线程...
### Java入门教程:数据类型与正确使用Volatile变量 #### 概述 在Java编程语言中,`volatile`关键字提供了一种轻量级的同步机制,用于确保共享变量的可见性和一定程度上的线程安全性。相比于传统的锁机制如`...
Java内存模型(Java Memory Model, JMM)是Java平台中用于规范线程间通信和内存可见性的重要概念,它的目标是确保多线程环境下的正确同步。然而,原始的JMM存在一些严重的缺陷,导致了开发者在理解和实现线程安全时...
2. 变量不需要与其他状态变量一起满足不变性条件:如果一个变量的值独立于其他状态变量,那么使用`volatile`可以避免引入锁来维护这些变量的不变性。 总结起来,`volatile`关键字提供了一种轻量级的同步机制,适用...
在实践中,开发者应该充分利用这些增强的特性,例如,利用volatile保证状态变量的正确传播,以及利用final确保不变对象的安全共享。理解并应用这些内存模型的改变对于编写高效、可靠的并发Java程序至关重要。通过JSR...
正确使用 volatile 变量的条件是:对变量的写操作不依赖于当前值,以及该变量没有包含在具有其他变量的不变式中。在实际应用中,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量...
《Java理论与实践:构建一个更好的HashMap》这篇文章深入剖析了Doug Lea的`util.concurrent`包中的`ConcurrentHashMap`实现,旨在展示如何在保证线程安全的同时提高并发性能。`ConcurrentHashMap`相较于传统的`...
### STM32中Volatile变量的正确使用 #### 概述 在嵌入式系统编程中,`volatile`关键字的正确使用对于确保程序的稳定性和可靠性至关重要。它主要用于标记那些可能在程序运行过程中被外部因素(如硬件中断、多线程...
加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相
使用`volatile`关键字可以确保在多个线程试图初始化同一个对象时,能够正确地处理可见性和有序性问题。 总结来说,`volatile`关键字是Java并发编程中一个非常重要的概念。它不仅解决了多线程环境下的可见性问题,还...
volatile变量在编程语言中,尤其是Java和C/C++中,是一种特殊的标识符,用于指示编译器该变量的值可能在编译器不知情的情况下发生变化。这个关键字的主要作用是告诉编译器不要对这个变量进行优化,每次使用时都需要...
这是通过Java内存模型(JMM)中的主内存与工作内存之间的交互来实现的,`volatile`变量的读写操作会强制刷新工作内存,确保了数据的一致性。 **有序性**: `volatile`关键字还能够防止指令重排序。在多线程环境下,...
1. **volatile与const结合使用**:理论上,一个变量可以同时被声明为`volatile`和`const`。例如,对于只读的状态寄存器,它可以被声明为`volatile const`,表明这个变量可能会被外部因素改变,但程序不应该尝试修改...
本文将详细探讨volatile关键字的工作原理、使用场景以及如何在实际开发中正确使用volatile。 volatile关键字是Java并发编程中一个重要的工具,它通过确保变量的可见性和禁止指令重排序来提高程序的并发性能。然而,...
### Java理论与实践:修复Java内存模型,第1部分 #### 概述 活跃了将近三年的JSR133近期发布了一项关于如何修复Java内存模型(Java Memory Model, JMM)的重要建议。原始JMM中存在一些严重的缺陷,这导致了原本被...
Java内存模型(JVM Memory Model,简称JMM)是Java语言规范中定义的一个抽象概念,它描述了在多线程环境下,各个线程之间共享变量的访问规则,以及在并发情况下如何保证数据的一致性。Java内存模型的主要目标是解决...
在Java编程语言中,`volatile`关键字是一个非常重要的并发控制机制,它被用来修饰类的成员变量,确保这些变量在多线程环境下的可见性和有序性。然而,使用`volatile`并非总是如我们所期望的那样简单,有时会出现一些...
本资料《深入探讨Java多线程中的volatile变量》将带你深入理解这个概念,全面解析其工作原理和实际应用。 volatile关键字在Java中主要用于解决多线程环境下的可见性和有序性问题。它确保了被volatile修饰的变量对...