Android volatile 原理。

在《Java 并发编程:核心理论》一文中,我们已经提到可见性、有序性及原子性 问题,通常情况下我们可以通过 Synchronized 关键字来解决这些个问题,不过 如果对 Synchonized 原理有了解的话,应该知道 Synchronized 是一个较重量级 的操作,对系统的性能有比较大的影响,所以如果有其他解决方案,我们通常都 避免使用 Synchronized 来解决问题。

而 volatile 关键字就是 Java 中提供的另一种解决可见性有序性问题的方案。对于 原子性,需要强调一点,也是大家容易误解的一点:对 volatile 变量的单次读/ 写操作可保证原子性的,如 long 和 double 类型变量,但是并不能保证 i++这种 操作的原子性,因为本质上 i++是读、写两次操作。 volatile 也是互斥同步的一种实现,不过它非常的轻量级。 volatile 的意义? 线程会一直等待。 可以尝试获得锁,线程可以不用一直等待 锁状态 无法判断 可以判断 锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可) 性能 少量同步 大量同步

  • 防止 CPU 指令重排序 volatile 有两条关键的语义: 保证被 volatile 修饰的变量对所有线程都是可见的 禁止进行指令重排序 要理解 volatile 关键字,我们得先从 Java 的线程模型开始说起。如图所示:


    image.png

    Java 内存模型规定了所有字段(这些字段包括实例字段、静态字段等,不包括局 部变量、方法参数等,因为这些是线程私有的,并不存在竞争)都存在主内存中, 每个线程会 有自己的工作内存,工作内存里保存了线程所使用到的变量在主内 存里的副本拷贝,线程对变量的操作只能在工作内存里进行,而不能直接读写主 内存,当然不同内存之间也 无法直接访问对方的工作内存,也就是说主内存是 线程传值的媒介。
    我们来理解第一句话:
    保证被 volatile 修饰的变量对所有线程都是可见的 如何保证可见性?
    被 volatile 修饰的变量在工作内存修改后会被强制写回主内存,其他线程在使用 时也会强制从主内存刷新,这样就保证了一致性。 关于“保证被 volatile 修饰的变量对所有线程都是可见的”,有种常见的错误理解:

  • 由于 volatile 修饰的变量在各个线程里都是一致的,所以基于 volatile 变 量的运算在多线程并发的情况下是安全的。 这句话的前半部分是对的,后半部分却错了,因此它忘记考虑变量的操作是否具有原子性这一问题。
    举个例子:
    private volatile int start = 0;
    private void volatile Keyword() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    start++;
                }
            }
        }; for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable); thread.start();
        } Log.d(TAG, "start = " + start);
    }
image.png

这段代码启动了 10 个线程,每次 10 次自增,按道理最终结果应该是 100,但是 结果并非如此。 为什么会这样?
仔细看一下 start++,它其实并非一个原子操作,简单来看,它有两步:
1、取出 start 的值,因为有 volatile 的修饰,这时候的值是正确的。
2、自增,但是自增的时候,别的线程可能已经把 start 加大了,这种情况下就有 可能把较小的 start 写回主内存中。
所以 volatile 只能保证可见性,在不符合以 下场景下我们依然需要通过加锁来保证原子性:

  • 运算结果并不依赖变量当前的值,或者只有单一线程修改变量的值。(要 么结果不依赖当前值,要么操作是原子性的,要么只要一个线程修改变量 的值) - 变量不需要与其他状态变量共同参与不变约束 比方说我们会在线程里加 个 boolean 变量,来判断线程是否停止,这种情况就非常适合使用 volatile。
    我们再来理解第二句话。 禁止进行指令重排序 什么是指令重排序?
    指令重排序是指指令乱序执行,即在条件允许的情况下直接运行当前有能 力立即执行的后续指令,避开为获取一条指令所需数据而造成的等待,通 过乱序执行的技术提供执行效率。 指令重排序会在被 volatile 修饰的变量的赋值操作前,添加一个内存屏障, 指令重排序时不能把后面的指令重排序移到内存屏障之前的位置。

你可能感兴趣的:(Android volatile 原理。)