java并发编程系列-可见性、原子性、有序性问题

181 阅读3分钟

可见性、原子性、有序性问题

1.可见性

可见性指的是当一个线程修改了共享变量后,其他线程能够立即得知这个修改。在单核的时候,线程都是在同一 CPU 上执行,一个线程对 CPU 缓存的操作对其它线程都是可见的。

但是有多核时,情况就发生了变化,每个 CPU 有自己的缓存,我们可以假设这样的场景,有两个线程修改同一变量,分别在在不同的 CPU 上执行,两个 CPU 首先会把内存中的变量加载到各自的缓存中,线程 1 操作的是 CPU1 上的缓存,线程 2 操作的是 CPU2 上的缓存,它们对彼此并不可见,所以这就存在可见性问题。

单核:

在这里插入图片描述

多核:

1571044095135

2.原子性

原子性指的是一个或者多个操作在 CPU 执行的过程中不被中断的特性,所以线程切换带来了原子性问题。Java 并发程序都是基于多线程的,操作系统为了充分利用 CPU 的资源,将 CPU 分成若干个时间片,在多线程环境下,线程会被操作系统调度进行任务切换。

我们可以看下面这个常见面试题:

下面哪些操作是原子性操作

int count =0;//1
count++;//2
int b = count;//3

我们分析上面三个操作,只有操作 1 是原子操作,操作 2 在编译器作用下等同于 count = count+1 从 CPU 的角度它会有三条指令:

  1. 将变量 count 从内存加载到 CPU 缓存
  2. 将变量 count+1
  3. 将结果写入内存

操作 3 也不是原子的,我们可以通过 javap 将代码翻译成字节码文件查看,同时我们假设有两个线程,一个线程执行了 int b = count 操作,另一个线程操作 count 值。这时候线程 1 获取的 b 就有问题。

3.有序性

有序性指的是程序按照代码的先后顺序执行编译优化,带来的有序性问题,编译器为了性能优化,会将代码执行顺序做一定程度的优化,但是这种优化在并发程序下会带来一些诡异的 Bug,甚至让我们无从解决。例如本来程序是:

int a = 10;
int b = 12;

经过编译器优化可能变成了:

int b = 12;
int a = 10;

从这个例子看,似乎对程序结果看不出有什么影响。我们再看一个经典的面试题,也是工作中经常接触到的模式——单例模式

如果在面试的时候让我们手写一个单例模式,我相信大多数同学都能信手拈来,例如用双重检验锁实现:

class A {

}


示例:1-1
public class Singleton {
    private static Singleton INSTANCE;
    private Singleton(){}
    public static Singleton getSingleton() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

写完这段代码,似乎好像完成了面试官要求的单例模式。一切看上去似乎很顺利,内心窃喜,这时候面试官可能会问你,你觉得这段代码有什么问题吗?问题就出现在 new Singleton();。

对于 CPU 来说创建这个对象,会有三个 CPU 指令:

  1. 分配内存空间
  2. 初始化对象
  3. INSTANCE 引用指向分配好的内存空间

但是重排序后执行顺序可能为 1-3-2,那么问题就来了,这时候引用指向了堆内存的一块地址,但是对象还没有初始化完成。

如果这时候有其它线程进来,发生了时间片切换,判断引用 INSTANCE 不等于 null,这时候返回了未初始化完成的对象,这时候使用该对象就会出问题。

所以问题就在指令重排序,我们可以使用 volatile 对指令禁止重排序,修改后的代码如下:

示例:1-2
public class Singleton {
    private static volatile Singleton INSTANCE;
    private Singleton(){}
    public static Singleton getSingleton() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}