并发编程幕后的故事
随着设备的不断迭代,相应的速度也变得更快。但是在发展的过程中有一个矛盾一直存在,即三者的速度差异。
快慢关系:CPU > 内存 > I/O,程序整体的性能取决于最慢的操作,即读写I/O设备,所以单方面的提升CPU性能是无效的。
为了合理利用CPU的高性能,平衡三者的速度差异,计算机体系结构、操作系统、编译程序做出了贡献,体现为:
- CPU增加了缓存,平衡与内存的速度差异
- 操作系统增加了进程、线程,以分时复用CPU,进而均衡CPU与I/O设备的速度差异
- 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用
现在所有程序都是在享受着这些成果,但是并发程序很多诡异的问题的根源也在这里。
源头一:CPU缓存导致的可见性问题
- 什么是可见性?
一个线程对共享变量的修改,另外一个线程能够立刻看到,称之为可见性
单核与内存的关系
所有线程都在一个CPU上执行,CPU缓存与内存的数据一致性容易解决。因为所有的线程操作的是同一个CPU的缓存,一个线程对缓存的写,另一个线程来说一定是可见的。
如下图:线程A和线程B都是操作相同的CPU缓存,所以线程A操作共享变量V的值,线程B访问V的值,一定是V的最新值(线程A写过的值)
多核与内存的关系
多核时代,每个CPU都有自己的缓存,那么保证CPU缓存与内存的数据一致性问题就不怎么容易了,当多个线程在不同的CPU上执行时,这些线程操作的是不同的CPU缓存。
如下图:有两个CPU,对应有两个CPU缓存,线程A和线程B操作在不同的CPU上执行,这时线程A操作的V的值就会对线程B不可见,同理线程B操作的V的值对线程A也不可见。
示例代码:
有一个共享变量count,两个线程t1和t2,线程t1和t2执行方法add10K(),每次add10K会循环10000次count ++操作。
public class Test1 {
private static long count = 0;
public static void main(String[] args) throws InterruptedException {
final Test1 test1 =