Java 并发编程实战(1)- 并发程序的 bug 源头
概述
并发编程一般属于编程进阶部分的知识,它会涉及到很多底层知识,包括操作系统。
编写正确的并发程序是一件很困难的事情,由并发导致的 bug,有时很难排查或者重现,这需要我们理解并发的本质,深入分析 Bug 的源头。
并发程序问题的源头
为了提升系统性能,在过去几十年中,我们一直在不断的提升硬件的设计,包括 CPU、内存以及 I/O 设备,但存在一个主要矛盾:三者之间速度有很大差异,CPU 最快,内存其次,I/O 设备最慢。
我们编写的程序,在运行过程中,上述三者都会使用到,在这种情况下,速度慢的内存和 I/O 设备就会成为瓶颈,为了解决这个问题,计算机体系结构、操作系统和编译程序做了如下改进:
CPU 增加了缓存,以均衡与内存的速度差异。
操作系统增加了进程、线程以及分时复用 CPU,从而均衡 CPU 与 I/O 设备的速度差异。
编译程序优化指令执行次序,使得缓存能够得到更加合理的利用。
并发程序的问题根源也基本来源于上述改进:
缓存引发的可见性问题
线程切换引发的原子性问题
编译优化引发的有序性问题
接下来我们分别展开描述。
缓存引发的可见性问题
什么是可见性?
可见性是说一个线程对共享变量的修改,另外一个线程能够立刻看到。
可见性问题是由 CPU 缓存引起的,它是在 CPU 变为多核后才出现的,单核 CPU 并不会存在可见性问题。
我们可以参考下面的示意图。
如图所示,当有 2 个线程同时访问内存中的变量 x 时,2 个线程运行在不同的 CPU 上,每个 CPU 缓存都会保存变量 x,线程运行时,会通过 CPU 缓存来操作 x,那么当线程 1 进行操作后,线程 2 并不会立刻得到更新后的 x,从而引发了问题。
我们来看下面的代码示例,它显示了对同一个变量使用多个线程进行加操作,最后判断变量值是否符合预期。
程序运行的结果如下。
我们可以看到,并不是每次返回的结果都是 60000。
线程切换引发的原子性问题
什么是原子性?
一个或者多个操作在 CPU 执行的过程中不被中断的特性,被称为原子性。原子性可以保证操作执行的中间状态,对外是不可见的。
CPU 可以保证的原子操作是在 CPU 指令级别的,并不是高级语言的操作符,而高级语言中的一个操作,可能会包含多个 CPU 指令。
以上述代码中的 count = count + 1 为例,它至少包含了三条 CPU 指令:
指令 1:首先需要把变量 count 从内存加载到 CPU 寄存器。
指令 2:在寄存器中执行+1 操作。
指令 3:将结果进行保存,这里可能会保存在 CPU 缓存,也可能保存在内存中。
上述指令执行过程中,可能会产生”线程切换“,如果多个线程同时执行相同的语句,那么因为线程切换,就会导致结果不是我们期望的。
原子性问题并不只在多核 CPU 中存在,在单核 CPU 中也是存在的。
编译优化引发的有序性问题
什么是有序性?
有序性是指程序按照代码的先后顺序执行。
编译器为了优化性能,有时候会改变程序中语句的先后顺序,一般情况下,这并不会影响程序的最终结果,但有时也会引发意想不到的问题。
我们以典型的单例模式为例进行说明,示例代码如下。
一般情况下,假设有两个线程 A、B 同时调用 getInstance() 方法,他们会同时发现 instance == null ,于是同时对 Singleton.class 加锁,此时 JVM 保证只有一个线程能够加锁成功(假设是线程 A),另外一个线程则会处于等待状态(假设是线程 B);线程 A 会创建一个 Singleton 实例,之后释放锁,锁释放后,线程 B 被唤醒,线程 B 再次尝试加锁,此时是可以加锁成功的,加锁成功后,线程 B 检查 instance == null 时会发现,已经创建过 Singleton 实例了,所以线程 B 不会再创建一个 Singleton 实例。
但是,如果我们仔细分析 getInstance()方法中的 new 操作,会发现它包含以下几步:
分配一块内存 M。
在内存 M 上初始化 SingletonDemo 对象。
将 M 的地址赋值给 instance 变量。
但编译器可能会做一些优化,变成下面的样子:
分配一块内存 M。
将 M 的地址赋值给 instance 变量。
在内存 M 上初始化 SingletonDemo 对象。
这样很可能导致线程 B 获取 instance 之后,在 instance 初始化没有完全结束的情况下,调用它的方法,从而引发空指针异常。
上述是我们常见的并发程序的 bug 源头,只要我们能够深刻理解可见性、原子性和有序性在并发场景下的原理,很多并发 bug 就很容易理解了。
参考资料:
版权声明: 本文为 InfoQ 作者【技术修行者】的原创文章。
原文链接:【http://xie.infoq.cn/article/1c7f598f35afa5061507d79c1】。文章转载请联系作者。
评论