关于原子性, 可见性,有序性的思考

原子性

原子性是指 “不会有中间状态存在,要么什么都没改变,要么全都改变”, “对数据操作的原子性”.

在并发编程中, 原子性存在的根本原因是, 多个线程操作共享变量, 由于线程间切换调度, 导致一个线程操作了另一个线程 " 半成品" 的数据, 这是导致多线程环境下结果不可预测的一个原因.

synchronized 的原子性保证
synchronized 提供的特性是互斥, 可重入, 不可中断, 要用 synchronized 保证共享变量操作的原子性, 则需要对共享变量的读写加锁访问.
Atomic包的原子性保证, 本质是 cas操作, 处理器保证原子性

可见性

可见性是指, 一个线程的操作结果能够立刻被其他线程观察到
多核心多缓存, 导致可见性问题.

synchronized的可见性保证
依赖于synchronized实现的两条语义

  1. 加锁时, 将工作内存置无效, 共享变量从主内存读取
  2. 解锁前, 将工作内存变量刷新到主内存

volatile的可见性保证
volatile实现中的lock前缀指令, 强制刷新到内存 && 置其他缓存行 数据无效

final 变量的可见性保证 (todo)

有序性

为了填充流水线, 充分发挥指令级并行的能力, 编译器要对代码进行重排序优化, 同时处理器也有一定的乱序执行优化.
故在线程内部, 程序执行 as if serial, 语义上保持有序性, 即乱序执行的结果能够保证和顺序执行的结果一致.
然而在线程外部观察, 所有操作都是无序的(语义上无序), 这种无序我更认为是 “重排序优化”“工作内存与主内存同步延迟” 的现象叠加. <深入理解JVM> - P374

volatile的有序性保证
volatile相关变量, 禁止指令重排序. 如双检锁实现中, 确保instance引用在对象未初始化成功前, 不能被其他线程观测到.

synchronized的有序性保证
看下面的双检锁实现

public class Singleton {

    private static volatile Singleton instance;

    private static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

看到这里我是懵逼的状态, 查看一些介绍都说synchronized 能够保证有序性, 那么为什么还要volatile ?
b乎上找到了一个很好的解释, "synchronized的有序性和volatile的有序性是从不同的角度来看待的"
线程与线程间, 每一个synchronized块可以看成是一个原子操作, 它解决了块与块之间同步延迟引发的无序

  • synchronized 是块与块之间满足原子性, 有序性, 可见性
  • volatile 底层通过内存屏障指令, 指令与指令之间有序, 线程之间可见.

你可能感兴趣的:(Java)