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 读操作,这通常要优于一个无竞争的锁获取的开销。