Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是不具备原子特性。
只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
1、对变量的写操作不依赖于当前值。
2、该变量没有包含在具有其他变量的不变式中。
例一:状态标志模式
volatile boolean shutdownRequested;
...
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
注:很可能会从循环外部调用 shutdown() 方法 ―― 即在另一个线程中 ―― 因此,需要执行某种同步来确保正确实现 shutdownRequested 变量的可见性。然而,使用 synchronized块编写循环要比使用 volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。
例二:一次性安全发布模式
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
// do lots of stuff
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
}
}
注:如果 theFlooble 引用不是 volatile 类型,doWork() 中的代码在解除对 theFlooble 的引用时,将会得到一个不完全构造的 Flooble。(对象引用在没有同步的情况下进行读操作,产生的问题是可能会看到一个更新的引用,但是仍然会通过该引用看到不完全构造的对象)
该模式的一个必要条件是:被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)。volatile 类型的引用可以确保对象的发布形式的可见性,但是如果对象的状态在发布后将发生更改,那么就需要额外的同步。
例三:独立观察模式
安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。要求被发布的值是有效不可变的 ―― 即值的状态在发布后不会更改。
例四:“volatile bean” 模式
在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 ―― 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile
时,只有引用而不是数组本身具有 volatile 语义)。对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession
)提供了容器,但是放入这些容器中的对象必须是线程安全的。
例五:开销较低的读-写锁策略模式
public class CheesyCounter {
private volatile int value;
public int getValue() {
return value;
}
public synchronized int increment() {
return value++;
}
}
注:如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。线程安全的计数器使用synchronized 确保增量操作是原子的,并使用 volatile 保证当前结果的可见性。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。