volatile关键字

 Java中的volatile关键字能够保证每次对变量的读操作直接从主存中读取,而非从Cache中读取;每次写操作都立即将Cache中数据同步到主存。


 在多线程环境中多个线程同时访问同一个非volatile变量,并且没有使用锁同步控制,那么运行在不同CPU上的多个线程都会从主存中读取一份变量的副本到Cache中,进而对Cache中的数据进行操作。不同线程中的操作对于彼此来说是不完全可见的,也就是说某个线程修改了变量的值,只是保存在Cache中,可能没有同步会主存,那么另一个线程对此修改不可见;或者一个线程的修改已经同步回主存,而另一个线程仍是从Cache中读取,也没有读取到最新值。



 定义为volatile的变量,当某个线程对其执行写操做,Cache中的数据会立马被写会主存,并且会引起其他CPU中对此数据的缓存失效,这样当其他线程在读取此变量时,就会从新从主存中读取,得到了最新的值。


 volatile变量仅能保证变量的可见性,而不能变量修改的原子性。即使变量i是volatile的,i++这样的操作在多线程环境中仍然得不到正确的结果。当两个线程同时从主存中读取i的值,然后在CPU中进行加1,这个时候,每个线程得到的值都是i+1,当两个线程均将得到的值写会主存时,结果就是i+1,而不是期望的i+2。


public class ThreadDemo {
    private static volatile int count = 0; // volatile静态变量

    private static class Increment implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) // 对count自增10w次
                count++;
        }
    }


    public static void main(String[] args) throws Exception {
        Increment increment = new Increment();
         // 开启两个线程对count进行自增10w次操作,期望的到20w
        Thread t1 = new Thread(increment);
        Thread t2 = new Thread(increment);
        t1.start();
        t2.start();
        t1.join(); // 等待线程结束
        t2.join(); // 等待线程结束
        System.out.println(count); // 某次输出:162867,结果远偏离期望的20w
    }
}
 

于性能考虑,JVM可能会对代码进行重排序操作,并且程序的结果不会受到重排序的影响。这在单线程环境中没有任何问题,但是在多线程环境中,重排序可能会导致不可预估的结果。


public class ThreadDemo {
    private static boolean prepared = false; // 非volatile的
    private static Object product = null;

    private static class Produce implements Runnable {
        @Override
        public void run() {
            // 此处可能发生重排序,(1)和(2)调换了顺序
            product = new Object(); // (1) 
            prepared = true; // (2)
        }
    }

    private static class Consumer implements Runnable {
        @Override
        public void run() {
            // 当(1)和(2)发生了指令重排序,会导致还没有真正创建product,此线程就
            // 误认为product已经准备好了。此处还存在可见性问题。
            while (!prepared) {
                // 等待
            }
            Object o = product;
        }
    }

    public static void main(String[] args) throws Exception {
        new Thread(new Produce());
        new Thread(new Consumer());
    }

 而volatile能够避免指令重排序带来的弊端,在volatile变量操作之前的指令重排序不会被排序到volatile变量操作之后,并且在volatile变量操作之后的指令重排序不会被排序到volatile变量操作之前。也就是说:(1)和(3)处的代码可能会发成重排序,而(1)处代码绝不会被排序到(2)之后,(3)处代码绝不会被排序到(2)之前。

someVariable1 = 1; // (1)
someVariable2 = 2; // (1)
volatileVariable = true; // (2) volatile变量操作
otherVariable1 = 3; // (3)
otherVariable2 = 4; // (3)


 volatile可用于状态标识,如布尔类型,当到达一定条件,某个线程修改它的值,其他线程读取此值判断是否执行后续步奏。


public class ThreadDemo {
    private static volatile boolean prepared = false; // volatile的
    private static Object product = null;

    private static class Produce implements Runnable {
        @Override
        public void run() {
            product = new Object();
            prepared = true; // 创建完product,会立即将prepared的值写入主存
        }
    }

    private static class Consumer implements Runnable {
        @Override
        public void run() {
            while (!prepared) { // 每次判断从主存读取prepared的值,能够及时读取到prepared的最新状态
                // 等待
            }
            Object o = product;
        }
    }

    public static void main(String[] args) throws Exception {
        new Thread(new Produce());
        new Thread(new Consumer());
    }
}


 volatile变量可用于读取量远高于写入量的情况。


class CheesyCounter {
    private volatile int value; // volatile变量的读开销接近非volatile变量的读开销

    public int getValue() { return value; } // volatile保证每次读取都能够读取到最新值

    public synchronized int increment() { // 对写入操作进行同步,确保线程安全
        return value++;
    }
}

推荐阅读:


 http://tutorials.jenkov.com/java-concurrency/volatile.html

 https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

 http://www.infoq.com/cn/articles/ftf-java-volatile#anch83440

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值