seve

简介

volatile关键字主要是用来解决共享变量内存可见性问题CPU指令乱序执行问题

下面通过一个实例来说明下这两个问题导致的原因和volatile如何解决这两个问题。

volatile的使用

public class TaskRunner {

    private static int number;
    private static boolean ready;

    private static class Reader extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }

            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new Reader().start();
        number = 42;
        ready = true;
    }
}

上面程序的执行结果除了延迟一段时间正常输出42之外,还可能存在以下两种结果:

1.线程永远不会停止

这种情况属于前面说的共享变量内存可见性问题。可见性是指当一个线程修改了某一个共享变量的值时,其他线程是否能够立即知道这个修改。

如下Java内存模型的抽象图所示,线程之间的共享变量都存储在主内存中,但每个线程本地内存都会存有共享变量的副本。假设前面例子中主线程和子线程分别在不同的CPU里,主线程将ready的值设置为true,这个值并不会立刻同步到主内存,所以子线程也就不能读取到最新的值,而读取的是本地内存变量副本的值。

2.线程结束,输出结果number值为0

这种情况属于前面说的CPU指令乱序执行问题。CPU的速度至少比内存快100倍,为了提升效率,会打乱原来的执行顺序,会在一条指令执行过程中,去同时执行另一条指令,当然乱序执行的前提是两条指令没有依赖关系

在单线程的情况下,重排序能够保证乱序执行结果和顺序执行结果是一致的,但是在多线程的情况下就可能会有问题了。如上可能会出现ready = true先执行,而number = 42则刚好在输出语句后执行。

使用volatile避免出现上述问题
public class TaskRunner {

    private volatile static int number;
    private volatile static boolean ready;

    //...
}

1.当一个变量被声明volatile时,线程对它的修改会直接刷新到主内存,其他线程对它的读取也是直接从主内存读取,这样就能保证所有线程读取到这个变量的值都是一致的。

2.volatile能够禁止指令重排序,在被声明volatile变量上面执行的指令不可以乱序。

volatile和synchronized的比较

volatile是轻量级的synchronized,它能保证可见性和有序性,但它不能保证原子性。之所以说volatile是轻量级的synchronized,是因为volatile的执行效率要更高,而synchronized是独占锁,会有锁的开销。

synchronized能够保证可见性,当线程进入synchronized块时,会先清空本地内存中的变量值,然后从主内存中读取最新的值。当线程退出synchronized块时,会将本地内存中的变量值同步到主内存。

synchronized块可以看作一个整体,同一时间只有一个线程运行synchronized块代码,不会被其他线程干扰,所以也就能保证原子性和有序性。

分类:

技术点:

相关文章: