在了解 Java 内存模型之前,我们先看下物理计算机中的并发问题。物理机中遇到的并发问题与虚拟机中遇到的问题有不少相似之处,物理机中对并发的解决方案对虚拟机有很大的参考意义。
硬件的效率与一致性
“让计算机并行执行多个任务”与提高计算机的运算效率并不像看上去那么顺理成章。大多数的计算任务都不可能只靠“运算”来完成,计算机至少要跟内存交互,执行 I/O 是计算机必不可少的一个操作。
计算机的运算速度和存储设备的读写速度一般相差几个量级,存储设备的读写成为影响计算机效率的关键。现在的解决方案是在 CPU 和内存之间加入一个高速缓存来作为 CPU 和内存之间的缓冲。读数据时事先将需要的数据从内存读取到高速缓存,写入时将计算结果先存储到高速缓存里,然后再将数据同步到内存中。
基于高速缓存的存储交互很好的解决了 CPU 和内存之间的速度问题,但又引入了一个新的问题「缓存一致性」。在多处理器系统中,每个处理器都有自己的缓存系统,同时他们又共享同一块主内存。当多个处理器的计算操作都涉及到同一块内存区域时,将可能导致各自的缓存不一致。
如果真发生这种情况,那同步回主内存的数据以谁的缓存数据为准呢?为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议进行操作,这类协议有 MSI、MESI 等。
「内存模型」一词,可以理解为在特定的操作协议下对特定的内存或高速缓存进行读写访问的过程抽象。不同的物理机器有不同的内存模型,而 Java 虚拟机也有自己的内存模型。
并发编程的两个关键问题
在并发编程中需要处理两个关键问题:线程之间如何通信以及线程之间如何同步。通信是指线程之间如何交换信息。在命令式编程中,线程之间通信机制有两种:共享内存和消息传递。
在共享内存并发模型里,线程之间共享程序的公共状态,通过读-写内存中的公共状态进行隐式通信。在消息传递的并发模型里,线程之间没有公共状体,线程之间必须通过发送消息进行隐式通信。
同步是指程序中用于控制不同线程之间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。程序员必须指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于发送消息的线程必须在接受消息之前,因此同步是隐式进行的。
Java 的并发采用的是共享内存模型,Java 线程之间的通信总是隐式进行,整个通信对程序员完全透明。如果编写多线程程序的 Java 程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。