text: JAVA系列之内存模型（JMM）
  Java内存模型是在硬件内存模型上的更高层的抽象，它屏蔽了各种硬件和操作系统访问的差异性，保证了Java程序在各种平台下对内存的访问都能达到一致的效果。 Java内存模型是不可见的，它并不是一个真实的东西，它只是一个概念、一个规范。早期计算机中CPU和内存的速度是差不多的，随着CPU高速发展，CPU的运算速度已经远远高于它从存储介质读取数据的速度，这里的存储介质有很多，比如磁盘、光盘、网卡、内存等。所以，在程序运行的过程中，CPU大部分时间都浪费在了磁盘IO、网络通讯、数据库访问上，如果不想让CPU在那里白白等待，我们就必须想办法去把CPU的运算能力压榨出来，让CPU同时去处理多项任务，这也就是我们常说的“并发执行”。但是，所有的运算都不可能只依靠CPU的计算就能完成，往往还需要跟内存进行交互，如读取运算数据、存储运算结果等。CPU与内存的交互往往是很慢的，所以现代计算机系统加入一层读写速度尽可能接近处理器运算速度的高速缓存（Cache）来作为内存与处理器之间的缓冲：基于高速缓存机制很好地解决了处理器与内存的速度矛盾，但是引入了一个新的问题：缓存一致性（Cache Coherence）。随着CPU能力的不断提升，一层缓存就无法满足要求了，就逐渐衍生出了三级缓存L1、L2、L3）。CPU会先在最快的L1中寻找需要的数据，找不到再去找次快的L2，还找不到再去找L3，L3都没有那就只能去内存找了。其中一级缓存还分为一级数据缓存（Data Cache，D-Cache，L1d）和一级指令缓存(Instruction Cache，I-Cache，L1i)，分别用于存放数据及执行数据的指令解码，两者可同时被CPU访问，减少了CPU多核心、多线程争用缓存造成的冲突，提高了处理器的效能。一般CPU的L1i和L1d具备相同的容量。解决这个缓存一致性问题的方法有很多，比如： 1.总线加锁（此方法性能较低，现在已经不会再使用）。 2.MESI协议： 这是Intel提出的，MESI协议也是相当复杂，在这里我就简单的说下：当一个CPU修改了Cache中的数据，会通知其他缓存了这个数据的CPU，其他CPU会把Cache中这份数据的Cache Line置为无效，要读取数据的话，直接去内存中获取，不会再从Cache中获取了。关于缓存一致性详细介绍：https://www.cnblogs.com/chanmufeng/p/16523365.html我们在Java中开启一个线程，最终也是交给CPU去执行。 具体的流程是：我们在使用Java线程，内部会调用操作系统(OS)的内核线程（Kernel-Level Thread），这种线程是操作系统内核（Kernel）直接支持的，内核通过调度器，对线程进行调度，并将线程交给各个CPU内核去处理。 如下图所示：除此之外，为了使CPU中的运算单元能够充分地被利用，CPU可能会对输入的代码进行乱序执行优化，然后在计算之后再将乱序执行的结果进行重组，保证该结果与顺序执行的结果一致，但并不保证程序中各个语句计算的先后顺序与代码的输入顺序一致，因此，如果一个计算任务依赖于另一个计算任务的结果，那么其顺序性并不能靠代码的先后顺序来保证。总的来说，硬件内存模型定义了共享内存系统中多线程读写操作行为的规范。为了解决上面提到的多个缓存读写一致性以及乱序排序优化的问题。本地内存：我们知道，Java里面每个线程都有一个自己的本地内存（上图绿色区域），存放的是私有变量和主内存数据的副本。如果私有变量是基本数据类型，则直接存放在本地内存，如果是引用类型变量，存放的是引用（指针），实际的数据存放在主内存。本地内存是不共享的，只有属于它的线程可以访问。也称之为线程栈或工作空间主内存：存放的是共享的数据，所有线程都可以访问。也有不少其他称呼，比如堆内存或共享内存Java内存模型规定了所有对共享变量的读写操作都必须在本地内存中进行，需要先从主内存中拿到数据，复制到本地内存，然后在本地内存中对数据进行修改，再刷新回主内存。所以，不管是Java内存模型中的本地内存，还是主内存的数据，最终都会存储在CPU（更准确的来说是寄存器、Cache、内存）上。Java内存模型就是为了解决多线程下共享数据的读写一致性问题关于主内存与工作内存之间具体的交互协议，Java内存模型定义了以下8种具体的操作来完成：Java内存模型还定义了执行上述8种操作的基本规则：注意，这里的lock和unlock是实现synchronized的基础，Java并没有把lock和unlock操作直接开放给用户使用，但是却提供了两个更高层次的指令来隐式地使用这两个操作，即moniterenter和moniterexit。上面介绍的内存模型同步操作与规则就是围绕原子性、可见性、顺序性三个特性来设计的。原子性是指一个操作是不可中断的。即使是多个线程一起执行的时候，一个操作一旦开始，就不会被其他线程干扰。上面四个操作，只有1才是原子操作，其余均不是要想在多线程环境下保证原子性，则可以通过锁、synchronized来确保一个线程在工作内存修改了共享内存中变量值，对其他持有该数据的线程不可见。 主要原因：• 共享变量更新后，工作内存与主内存未及时更新• 重排序结合线程交叉执行针对可见性，可通过在变量上增加volatiles、ynchronized和final关键字解决• volatile变量在修改之后立即同步回主内存，以及在每次读取前立即从主内存刷新• synchronized的可见性，JMM关于synchronized的两条规定： 1）线程解锁前，必须把共享变量的最新值刷新到主内存中 2）线程加锁时，将清空工作内存中共享变量的值，从而使用共享变量时需要从主内存中重新获取最新的值• final的可见性是指被final修饰的字段在构造器中一旦被初始化完成，那么其它线程中就能看见这个final字段了。代码在运行的时候，执行顺序可能并不是严格从上到下执行的，会进行指令重排。根据CPU流水线作业，一般来说简单的操作会先执行，复杂的操作后执行。 指令重排两个规则：• as-if-serial：不管怎么重排序，单线程的执行结果不能发生改变。正是由于这个特性，在单线程中，程序员一般无需理会重排序带来的问题。• happens-before：先行发生原则，如果两个操作的执行次序无法从happens-before原则推导出来，那么它们就不能保证它们的有序性，虚拟机可以随意地对它们进行重排序Java中提供了volatile和synchronized两个关键字来保证有序性• volatile天然就具有有序性，通过插入内存屏障禁止重排序。• synchronized的有序性是由“一个变量同一时刻只允许一条线程对其进行lock操作”这条规则获取的。1、硬件内存架构使得必须建立内存模型来保证多线程环境下对共享内存访问的正确性；2、Java内存模型定义了保证多线程环境下共享变量一致性的规则；3、Java内存模型提供了工作内存与主内存交互的八大同步操作和八大规则；4、Java内存模型三大特性原子性、可见性、有序性介绍，以及实现方式；