多线程编程学习十二(原子性、可见性与有序性)

多线程编程学习十二(原子性、可见性与有序性)

原子性

原子(atom)指化学反应不可再分的基本微粒,原子在化学反应中不可分割。原子操作指的是不可分割的整体,多线程的原子性指的是没有其他线程能够中断或检查正在原子操作中的变量。

从内存模型来看,直接保证的原子性变量操作包括 read、load、assign、use、store 和 write,我们大致可以认为基本数据类型的访问读写是具备原子性的。

从应用场景来看,JVM 保证原子性操作的主要有以下方式:

  • synchronized 关键字。锁操作,基于 monitorenter 和 monitorexit 字节码指令,保证同步块只有单一线程执行。
  • AQS 锁机制。比如 ReentrantLock、ReentrantReadWriteLock 等,保证同步块只有单一线程执行。
  • CAS 实现。比如 java.util.concurrent.atomic 包中的诸多实现。
  • volatile 关键字。修饰变量,轻量锁机制,仅能保证对单个变量的操作具有原子性,复合操作不具备原子性。

可见性

可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

从应用场景来看,JVM 保证可见性主要有以下方式:

  • volatile 关键字,它是如何保证可见性的呢?

    • 当对 volatile 变量写的时候,会将当前处理器缓存行的数据写回到系统内存。
    • 当对 volatile 变量读的时候,会将当前处理器缓存行的数据置为无效,因此要从系统内存中读取变量值。
  • synchronized 等锁机制。同步块的可见性是由“对一个变量执行 unlock 操作之前,必须先把此变量同步回主内存中(执行 store、write 操作)”这条规则获得的。
  • final 关键字。被 final 修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去,那在其他线程中就能看到 final 关键字。
    private static final int i;

    private final int j;

    static {
        i = 0;
    }

    {
        // 也可以选择在构造函数中初始化
        j = 0;
    }

有序性

有序性是指如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行的语义(as is serial)”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。

从应用场景来看,JVM 保证有序性主要有以下方式:

  • volatile 关键字,它本身就包含了禁止指令重排序的语义。
  • synchronized 等锁机制,同步块的有序性是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的。