Java内存模型详解
在前面一篇文章Java运行时数据区域中提到Java内存模型,是用来控制线程之间的通信。很好奇Java内存模型是如何控制线程之间通信的,带着疑问开始今天的学习。
为什么需要线程同步
在解决这个问题之前,首先得明白,多线程是如何实现的?
通过CPU的时间分片机制,操作系统协调CPU在某个时间点,执行某个线程,因为CPU在线程之间切换比较快,给人的感觉,就好像多个任务在同时运行。线程之间会进行资源的抢占,例如,当线程A在对某个变量n进行操作还没有完成,另外一个线程B就进来了,那么此时B线程获取到的变量n就是线程A还没有操作完的变量,这样就会造成数据的脏读,产生线程安全问题,所以需要线程同步,确保多线程之间的变量的可见性。因此线程同步是保证线程安全的前提。
关于重排序
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三类:
1、编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2、指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3、内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
由于在编译和执行期重排序的存在,使得各个线程之间并不是按照java语句得顺序先后执行,而是随机执行得,这也是导致了非同步多线程得安全问题得原因之一。
JMM 属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。
处理器重排序
处理器并不直接向内存中写入数据,而是通过向高速缓冲区写入数据,然后由高速缓冲区向内存写入数据,这样可以保证运行效率,而不必一直等待写操作完成才去执行相应得操作,所以处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致!
Java内存模型
Java内存模型是就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能得到一致效果的机制及规范。目的是解决由于多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题[1]。
原子性[2]
线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。所以在多线程场景下,就会发生原子性问题。因为线程在执行一个读改写操作时,在执行完读改之后,时间片耗完,就会被要求放弃CPU,并等待重新调度。这种情况下,读改写就不是一个原子操作。即存在原子性问题。
缓存一致性[2]
在多核CPU,多线程的场景中,每个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的caehe中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同。
在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。
有序性[2]
除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是有序性问题。
针对上面的这些问题,不同的操作系统都有不同的解决方案,而Java语言为了屏蔽掉底层的差异,定义了一套属于Java语言的内存模型规范,即Java内存模型。
Java内存模型的实现
- 共享内存:使用volatile关键字
使用volatile关键字来修饰变量,volatile关键字会禁止指令重排,改变了变量的可见性。一个线程修改了变量,另一个变量会感知并执行相应的业务。实际上,使用volatile修饰的变量,线程每次在使用变量时,不从寄存器取值,而是从主存当中取得变量的值,相应的,当一个线程对变量进行了修改,也会将修改之后的变量写入主存,正式因为如此,才会保证线程取到变量的值是最新的。
图1 共享内存线程间通信示意图
- 消息传递:使用Object类的wait()方法和notify()方法
wait和notify的使用必须在同一synchrnoized范围内。synchronized关键字保证同一时刻只允许一条线程操作。但是synchronized是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。
图2 使用Object类的wait()方法和notify()方法线程间通信示意图
总结
Java内存模型,其实是保证了Java程序在各种平台下对内存的访问都能够得到一致效果的机制及规范。目的是解决由于多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题。
参考:
[1] 周志远, 张大方, 缪力,等. 基于Java内存模型的并发程序模型检测[C] 中国测试学术会议. 2008:111-114.
[2] 深入理解Java虚拟机[M]. 2011.