JohnShen's Blog.

[回顾并发基础] 并发问题的根源

字数统计: 1.2k阅读时长: 4 min
2019/08/24 Share

正式开启并发的回顾整理,札记主要记录知识点、典型Case、自己的想法、思维导图以及好的文章,侧重于知识的理解与整理,毕竟要在并发上有自己独到的理解还是欠火候的。以后回顾或学习到新内容,会不断对文章进行更新,以作长期知识储备。

并发编程问题的根源

1. CPU缓存导致“可见性”问题

单核时代,所有线程操作同一个CPU的缓存,CPU缓存和内存间不会存在数据不一致问题。

一个线程对共享变量的修改,另一个线程能立刻看到,即为“可见性”。

多核时代,每个CPU都有自己的缓存,这就容易造成数据一致性问题。例如对应特定变量V,线程A操作CPU1上V的缓存,线程B操作CPU2上V的缓存,前者对V的操作于后者而言不具备可见性。这就是缓存一致性问题。

缓存一致性(Cache Coherence)

计算机的CPU与内存间有一层高速缓存作为缓冲,用于解决处理器与内存之间的速度矛盾。在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存。如果某一处理器将数据写会内存,其他处理器上的值是旧的,执行计算操作会有问题。为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期。

样例:多线程并发执行count += 1,结果往往可能和想象结果不一样。两个相同的线程同时执行,第一次都会将 count = 0 读入自己的CPU缓存,执行完之后各自的CPU缓存中的值都是1,同时写入内存后,内存中的值为1。

2. 线程切换带来的“原子性”问题

现代操作系统的CPU调度都是使用时间片算法,经过特定时间片之后便会进行任务切换。曾经的操作系统基于进程调度CPU,不同进程不共享内存空间,进程切换要切换内存映射地址。而进程内的所有线程共享同一个内存区间,使用线程进行任务切换成本较低,所以现代操作系统更多基于轻量的线程进行调度,常说的CPU任务切换特指线程切换。

高级语言中的一条语句往往需要多个CPU指令完成。如 count += 1,需要三条指令:

  1. 将 count 值从内存中加载到CPU的寄存器中;
  2. 在CPU寄存器中执行 +1 操作;
  3. 将结果写会内存(也可能写回 CPU 高速缓存)。

而操作系统的任务切换是可以发生在任何一条CPU指令执行完,而非高级语言的一条语句。如果两个线程执行 count 操作,线程A执行指令1后,CPU切换到线程B,线程执行完 +1 操作后,将1写回内存,后又切换至线程A,线程A执行 +1 命令的结果是1,后又将count=1的结果写入内存。

一个或者多个操作在 CPU 执行的过程中不被中断的特性称为“原子性”。CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符。

3. 编译优化带来的“有序性”问题

编译器有时为了优化性能,有时会更改代码中的先后执行顺序。

示例:双重检查锁(无volatile)

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
static Singleton instance;
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}

执行new操作实际上执行的是:

  1. 分配一块内存M;
  2. 在内存M上初始化对象;
  3. 将 M 的地址赋值给示例对象。

但优化后的执行路径可能将2与3互换,当一个线程执行到内存地址赋值,切换到另一个线程,它会在第一个为空判断时直接返回 instance 对象,但 instance 对象没有初始化过,这时使用 instance 可能发生空指针异常。


Case

Q: 32位机器对 long 类型变量进行加减操作存在并发隐患的原因。

A: 因为64位的 long 类型在32位的机器上的操作必然是由多条CPU指令组合而成,是无法保证原子性的。


Reference

https://time.geekbang.org/column/article/83682

CATALOG
  1. 1. 并发编程问题的根源
    1. 1.1. 1. CPU缓存导致“可见性”问题
      1. 1.1.0.1. 缓存一致性(Cache Coherence)
  2. 1.2. 2. 线程切换带来的“原子性”问题
  3. 1.3. 3. 编译优化带来的“有序性”问题
  • 2. Case
    1. 2.0.1. Reference