本文试图向大家解释清楚JMM及其抽象模型,但不仅仅是一个介绍,更希望能讲清楚JMM内存模型抽象的原因。
一、JMM的概念;
二、JMM的抽象将内存内存模型分成线程私有的本地内存和所有线程共享的主存;
三、JMM抽象模型造成了并发编程中共享变量的内存可见性问题,为什么会造成?选择这样的抽象模型有什么好处?有什么样的方法来处理这个问题?
一、JMM
JMM直译过来就是java内存模型(java memory model),他的更深层次的描述,“JMM是一个语言级的内存模型,通过屏蔽各个系统平台的差异,对程序猿提供一个各个系统平台一致的内存模型,他描绘了共享变量在内存中存取的抽象过程,规定了一个线程操作共享变量的结果何时对其他线程可见。”
二、JMM抽象
java的并发模型是基于内存共享的,通过共享内存之间的公共状态来进行线程通信,这种共享实际上就是共享变量的读写操作。
除了上述java的并发模型,还有两条线索引出java内存模型的抽象。第一条,“速度线索”,按照速度从快到慢,“CPU> 寄存器> 高速缓存> 内存> 磁盘缓存> 磁盘”,高速缓存是为了缓解寄存器和内存之间的速度差异,同样的磁盘缓存缓解了内存和磁盘之间的速度差异;第二条,“总线事务线索”,在多CPU的系统中,无论何时,都只有一个CPU能操作内存,这一系列CPU和内存之间的竞争和通信通过总线事务(包含读写事物)来描述,如下图。
基于以上两条线索(细节在下一节描述),java内存模型的抽象可以这样来描述,“每个运行的线程都有一个私有的本地内存,所有线程共享主存,所有线程对共享变量的读操作都首先从本地内存获取,如果没有则从主存获取;所有线程的写操作都首先写到本地内存,在恰当的时机写回主存”。JMM的抽象模型看起来如下图所述。
以上内容通过“基于内存并发模型、速度线索和总线事务线索”引出JMM的抽象模型。还有一些没有讲清楚的地方,下面需要更详细的解释。
三、更多细节
为什么JMM的抽象结果要划分成线程私有的本地内存和所有线程共享的主存?
1. 系统模型和JMM的对应
在谈论更多细节之前,先强行把速度线索中描绘的硬件和JMM抽象模型对应,实际上JMM的抽象也基于此,要结合计算机组成原理来理解这部分内容。
2. 本地内存引发的并发问题
首先明确的前提是java的并发模型是基于内存读写共享变量来进行通信的,线程之间要进行通信,只有通过读写主存,也就是JMM通过控制每个线程的本地内存与主存之间的交互,来向程序猿提供内存可见性保证。基于这样的描述,其实JMM是可以跳开本地内存的,直接操作主存进行线程通信会更简单更可靠。引入本地内存还会导致一些不好的结果,如下图所述,A/B两个线程并行操作一个拥有共享变量(实际上就是对外可以访问的实例变量)的对象,该对象初始化完成的时候a、b变量都进行了默认初始化,即a=b=0,存在堆内存中(主存),操作开始之前,AB两个线程都会获得ab变量的副本存在于本地内存当中,当执行到a=1的时候,将1赋值给本地内存中的a,注意是本地内存,而不是主存中的a,同样的b=2,也只是将2赋值给B线程中的b变量而并没有写到主存,此时对于xy的赋值操作,都会从主存中获取,从而出现一个可能的执行结果,a=1,b=2,x=0,y=0。
然而直接操作主存,就不会出现这样的结果,因为任意时刻都只会有一颗CPU获得总线事务,也就意味着任意时刻,都只有一个线程可以操作共享变量,但是基于此模型的代码执行速度谁能忍受?为了表明这一原因,“速度线索”中高速缓存缓解内存和寄存器之间的速度不匹配问题,“缓解”的过程是将刷新到主存的内容保存到高速缓存中,积累到一定量再一次性刷到内存(此时其他线程才可见),这样的好处可以通过java传统IO流中缓冲流提高速度的原理想象得到,正如“总线事务线索”中描述那样,集中刷主存,减少了总线事务竞争的次数和总线占用,从而获得更高的性能。
3. 解决问题
如何解决?这正是多线程中同步的意义。同步做了那些事情?同步保证了原子性、互斥性、内存可见性和顺序系(禁止重排序)。同步的内容谈起来也有很多内容,不只是一个简简单单的使用synchronized或Lock,而更多的是希望能讲明白synchronized和lock表达的内存语义,这一部分会抽时间单独来写。
四、补充
本文只涉及java并发编程最开始的一些浅显内容,其他更多的内容并未涉及,后续会抽时间逐步完善。
附注:
本文如有错漏,烦请不吝指正,谢谢!
版权声明:本文为博主原创文章,未经博主允许不得转载。