Java volatile关键字实例

来源:互联网 发布:知乎成都旅游 编辑:程序博客网 时间:2024/06/01 13:10

  一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2)禁止进行指令重排序。

volatile关键字能保证可见性和有序性,但是不保证原子性。因此并不能保证线程安全。

看一个相关的例子:双重校验锁实现的单例模式:

public class DoubleCheckSymbol {    private static volatile DoubleCheckSymbol d;    private DoubleCheckSymbol() {}    public static DoubleCheckSymbol getSymbol() {        if (d == null) {            synchronized(DoubleCheckSymbol.class) {                if (d == null) {                    d = new DoubleCheckSymbol();                }            }        }        return d;    }}

这个单例模式中为什么要加volatile关键字呢?

如果不加volatile的话,会有如下隐患:

d = new DoubleCheckSymbol()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1, 给d分配内存。
2, 调用 DoubleCheckSymbol的构造函数来初始化成员变量。
3, 将d对象指向分配的内存空间(执行完这步 d就为非 null 了)。

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,但是由于它并未初始化,所以就可能发生错误。

此例中,synchronized关键字已经解决了原子性问题。同时也解决了可见性问题,因为synchronized能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。

然而有序性问题并没有解决,所以,这就是在这里使用volatile的目的,即为了防止指令顺序的重排序

另外,如果在JDK1.5之前这样用volatile的话,可能会出现异常结果。此前的JDK中即使将变量声明为volatile也不能完全避免重排序导致的问题(主要是volatile变量前后的代码仍然存在重排序问题)。

再看一个例子:

package com.lwc.test;import java.util.concurrent.CountDownLatch;public class Counter {    private static volatile int value;    private static CountDownLatch countDownLatch = new CountDownLatch(10000);    public static void main(String[] args) throws Exception{        for (int i=0;i<10000;i++){            new Thread(){                @Override                public void run() {                    increment();                    countDownLatch.countDown();                }            } .start();        }        countDownLatch.await();        System.out.println(getValue());    }    public static int increment(){        return value ++;    }    public static int getValue(){        return value;    }}

输出:可能是10000,也可能是小于10000的数,同样是因为volatile不能保证原子性(value ++并不是原子性操作,所以会出现两个线程同时取得了相同的value值,然后分别+1,然后各自写入内存,结果value只增加了1的情况)。

更详细的解释,可以参考前人总结:
http://www.cnblogs.com/dolphin0520/p/3920373.html