2.1 - 可见性/原子性和有序性问题
这三个问题是并发编程的bug源头。
源头1: cpu缓存导致的可见性问题
一个线程对共享变量的修改,另一个线程是否能够立即看得到,这个问题我们称之为可见行。
多核时代,每个cpu核心都有自己的缓存,各个核心的缓存和内存之间的数据一致性就很难解决。
源头2: 线程切换带来的原子性问题
在一个时间片内,如果进程需要进行io操作
- 这个进程可以标记自己为休眠状态并让出cpu
- 等io操作结束,操作系统把休眠的进程唤醒,唤醒后重新获得cpu的使用权
让进程休眠的原因:让cpu在这段等待时间里可以做别的事情,这样cpu的使用率就上来了
线程切换大多数发生在时间片结束的时候。操作系统做线程切换,是发生在任何一条cpu指令结束后,而不是高级语言里的一条语句。
源头3: 编译优化带来的有序性问题
只要我们能够深刻理解可见性、原子性、有序性在并发场景下的原理,很多并发 Bug 都是可以理解、可以诊断的
在介绍可见性、原子性、有序性的时候,特意提到缓存导致的可见性问题,线程切换带来的原子性问题,编译优化带来的有序性问题,其实缓存、线程、编译优化的目的和我们写并发程序的目的是相同的,都是提高程序性能。但是技术在解决一个问题的同时,必然会带来另外一个问题,所以在采用一项技术的同时,一定要清楚它带来的问题是什么,以及如何规避。
2.2 - Java内存模型
上一节提到的多核cpu缓存可见性问题,编译优化带来的有序性问题,是客观存在的,是任何一门语言都要去面对和解决的问题。
而Java的内存模型就是用来解决这两个问题的方案。
Java内存模型解决这个两个问题的思路是:按需禁用缓存和编译优化。按需禁用就是按照程序员的要求来禁用缓存。