Java内存模型-(1)

在介绍Java内存模型以前,先来看下计算机的内存模型,而后再来看Java内存模型在计算机的内存模型基础上作了哪些事情。程序员

要说计算机的内存模型,首先来看下为什么要有内存模型?编程

1、为何要有内存模型

首先,“内存模型”是一个与计算机硬件有关的概念,先来看下有什么关系?缓存

CPU和缓存一致性

  1. 内存速度知足不了CPU的读写速度:计算机在执行程序的时候,每条指令都是在CPU中执行的,且免不了要和数据打交道,而计算机上面的数据,是存放在主存当中的,也就是计算机的物理内存啦。刚开始,还相安无事的,可是随着CPU技术的发展,CPU的执行速度愈来愈快。而因为内存的技术并无太大的变化,因此从内存中读取和写入数据的过程和CPU的执行速度比起来差距就会愈来愈大,这就致使CPU每次操做内存都要耗费不少等待时间。安全

  2. CPU和内存之间增长高速缓存:但是,不能由于内存的读写速度慢,就不发展CPU技术,总不能让内存成为计算机处理的瓶颈吧。因此,人们想出来了一个好的办法,就是在CPU和内存之间增长高速缓存。缓存的概念你们都知道,就是保存一份数据拷贝。他的特色是速度快,内存小,而且昂贵。那么,程序的执行过程就变成了:当程序在运行过程当中,会将运算须要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就能够直接从它的高速缓存读取数据和向其中写入数据,当运算结束以后,再将高速缓存中的数据刷新到主存当中。多线程

  3. 衍生出多级缓存:而随着CPU能力的不断提高,一层缓存就慢慢的没法知足要求了,就逐渐的衍生出多级缓存。按照数据读取顺序和与CPU结合的紧密程度,CPU缓存能够分为一级缓存(L1),二级缓存(L3),部分高端CPU还具备三级缓存(L3),每一级缓存中所储存的所有数据都是下一级缓存的一部分。这三种缓存的技术难度和制形成本是相对递减的,因此其容量也是相对递增的。那么,在有了多级缓存以后,程序的执行就变成了:当CPU要读取一个数据时,首先从一级缓存中查找,若是没有找到再从二级缓存中查找,若是仍是没有就从三级缓存或内存中查找。并发

  4. 多线程、多CPU出现问题:单核CPU只含有一套L1,L2,L3缓存;若是CPU含有多个核心,即多核CPU,则每一个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存。编程语言

    单线程、单CPU核心性能

    cpu核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题。优化

    多线程、单CPU核心操作系统

    进程中的多个线程会同时访问进程中的共享数据,CPU将某块内存加载到缓存后,不一样线程在访问相同的物理地址的时候,都会映射到相同的缓存位置,这样即便发生线程的切换,缓存仍然不会失效。但因为任什么时候刻只能有一个线程在执行,所以不会出现缓存访问冲突。

    多线程、多CPU核心

    每一个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不一样的核心上执行,则每一个核心都会在各自的caehe中保留一份共享内存的缓冲。因为多核是能够并行的,可能会出现多个线程同时写各自的缓存的状况,而各自的cache之间的数据就有可能不一样。

    在CPU和主存之间增长缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每一个核的本身的缓存中,关于同一个数据的缓存内容可能不一致。

处理器优化和指令重排

上面提到在在CPU和主存之间增长缓存,在多线程场景下会存在缓存一致性问题。除了这种状况,还有一种硬件问题也比较重要。那就是为了使处理器内部的运算单元可以尽可能的被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化。除了如今不少流行的处理器会对代码进行优化乱序处理,不少编程语言的编译器也会有相似的优化,好比Java虚拟机的即时编译器(JIT)也会作指令重排

可想而知,若是任由处理器优化和编译器对指令重排的话,就可能致使各类各样的问题。

2、并发编程的问题

并发编程存在的问题:原子性问题、可见性问题、有序性问题。这是人们抽象定义出来的。而这个抽象的底层问题就是前面提到的缓存一致性问题、处理器优化问题和指令重排问题等。

并发编程,为了保证数据的安全,须要知足如下三个特性:

原子性:指在一个操做中就是cpu不能够在中途暂停而后再调度,既不被中断操做,要不执行完成,要不就不执行

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看获得修改的值。

有序性:程序执行的顺序按照代码的前后顺序执行。

不难发现,缓存一致性问题其实就是可见性问题。而处理器优化是能够致使原子性问题的。指令重排即会致使有序性问题。

3、什么是内存模型

缓存一致性问题、处理器器优化的指令重排问题是硬件的不断升级致使的。那么,有没有什么机制能够很好的解决上面的这些问题呢?

最简单直接的作法就是废除处理器和处理器的优化技术、废除CPU缓存,让CPU直接和主存交互。可是,这么作虽然能够保证多线程下的并发问题。可是,这就有点因噎废食了。

因此,为了保证并发编程中能够知足原子性、可见性及有序性。有一个重要的概念,那就是——内存模型。

定义

为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操做行为的规范。

目的

解决CPU多级缓存、处理器优化、指令重排等致使的内存访问问题,保证并发场景下的一致性、原子性和有序性。

4、Java内存模型

前面介绍过了计算机内存模型,这是解决多线程场景下并发问题的一个重要规范。那么具体的实现是如何的呢,不一样的编程语言,在实现上可能有所不一样。

定义

Java程序是须要运行在Java虚拟机上面的,Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各类硬件和操做系统的访问差别的,保证了Java程序在各类平台下对内存的访问都能保证效果一致的机制及规范。

Java内存模型规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存,线程的工做内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存。不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量的传递均须要本身的工做内存和主存之间进行数据同步进行。而JMM就做用于工做内存和主存之间数据同步过程。他规定了如何作数据同步以及何时作数据同步。

主内存和工做内存

能够简单的类比成计算机内存模型中的主存和缓存的概念。特别须要注意的是,主内存和工做内存与JVM内存结构中的Java堆、栈、方法区等并非同一个层次的内存划分,没法直接类比。《深刻理解Java虚拟机》中认为,若是必定要勉强对应起来的话,从变量、主内存、工做内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分。工做内存则对应于虚拟机栈中的部分区域。

JMM

是一种规范,目的是解决因为多线程经过共享内存进行通讯时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

Java内存模型的实现

在Java中提供了一系列和并发处理相关的关键字,好比volatile、synchronized、final、concurren包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。

原子性

在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。这两个字节码,在Java中对应的关键字就是synchronized。所以,在Java中可使用synchronized来保证方法和代码块内的操做是原子性的。

可见性

Java内存模型是经过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存做为传递媒介的方式来实现的。Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后能够当即同步到主内存,被其修饰的变量在每次是用以前都从主内存刷新。所以,可使用volatile来保证多线程操做时变量的可见性。除了volatile,Java中的synchronized和final两个关键字也能够实现可见性。只不过实现方式不一样,这里再也不展开了。

有序性

在Java中,可使用synchronized和volatile来保证多线程之间操做的有序性。实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只容许一条线程操做。

经过以上能够发现,好像synchronized关键字是万能的,他能够同时知足以上三种特性,这其实也是不少人滥用synchronized的缘由。可是synchronized是比较影响性能的,虽然编译器提供了不少锁优化技术,可是也不建议过分使用。

相关文章
相关标签/搜索