1.背景JMM(Java Memory Model)的提出,主要基于以下的几种原因:不同操作系统平台的内存模型不同,而Java又想做到Write Once Run Everywhere(即跨平台),那么必须要自己提供一套内存模型以屏蔽不同操作系统在内存模型方面的差异。由于除了编译器层面可以进行指令重排外,处理器层面也可以,尽管指令重排在一定程度上能够提升程序运行的效率,但这仅限于单线程环境下,一旦处在多线程环境,这种指令重排带来的更多的是并发性问题(比如多级缓存与内存中的数据不一致),而Java又是多线程语言,那么就会存在多线程编程。为了保证开发者编写的并发程序能够按照预期安全地执行,Java必须提出一套并发编程相关的规范(比如happens-before原则)来解决多线程环境下的可能存在的并发性问题。2.内容2.1 内存规范2.1.1 内存抽象
本地/工作内存:每个线程私有且独占,不可跨线程访问的物理内存区域。存放了共享变量的副本。主内存:线程间共享的逻辑内存区域(并不真实存在)。存放所有线程创建的实例对象,包括:成员变量、局部变量、类信息、常量、静态变量等。线程间通信必须借助主内存来进行。2.1.2 内存操作
下面的8种操作主要用于主内存与工作内存的数据交互:锁定(lock):将主内存中的一个共享变量标记为线程独享变量。读取(read):将主内存中的一个共享变量传递到线程的工作内存中。载入(load):将从主存(通过read)读到的共享变量装载进工作内存中的相应的共享变量副本中。使用(use):将共享变量的副本传递给执行引擎,供其使用。(每次需要用到该变量时,都会执行该指令)赋值(assign):将从执行引擎接收到的结果值赋值给共享内存的副本。(每次的赋值操作均会调用该指令)存储(store):将共享变量的副本传递回主内存中。写入(write):将接收到的共享变量的副本(新修改的共享变量)写回主内存中的相应的共享变量中。解锁(unlock):将主内存中的一个共享变量的线程独享变量的状态标记取消。注:
JMM规范中并没有明确说明read和store将读到的共享变量(副本)存放在工作内存/主内存的什么地方,但是肯定没有存发在共享变量(副本)对应的槽位上(类似于找一个地方暂存一下数据的意思),因为它需要load和write指令正式存放到相应的变量槽位上去。要求:同一时间,只能有一个线程持有某一共享变量的锁(通过lock指令获取),获得锁的线程可以多次加锁(同一个),但是释放锁时需要释放同等次数(类似于ReentrantLock)。使用共享变量之前,必须先读取并载入,即不允许在工作内存中读取一个凭空诞生(主内存中没有)的变量。不允许写回一个没有赋值的共享变量副本。(这样做你觉得有意义?😅)......(此处省略很多字)2.2 并发规范2.2.1 happends-before原则一句话概括就是:前一个操作的结果对后一个操作是可见的,无论这两个操作是否处在同一个线程里。8项原则:程序顺序原则:在同一个线程内的代码中,写在前面的代码happens-before写在后面的代码。锁规则:加锁happens-before释放锁。volatile规则:对于一个volatile变量的写操作happens-before volatile变量的读操作。(该原则并不是说你只能先写后读一个volatile变量,而是说,前一个操作如果是写入volatile变量的操作,那么该volatile变量的新值对于后续的volatile读操作一定是可见的)start规则:线程的start()动作happens-before线程内的每个动作。join规则:线程内的所有动作happens-before线程的join()动作。interrupt规则:对线程调用interrupt() happens-before线程自己检测到中断事件的发生。finalize规则:一个对象的构造函数的执行、结束和返回happens-before这个对象finalize()方法的开始。传递性:A happens-beforeB,B happens-before C,那么就有A happens-before C。3.注意事项3.1 JMM与JVM内存结构的区别JMM:与Java并发编程相关,抽象了线程与主内存的关系,规定了并发相关的原则和规范,以简化多线程编程,增强程序的可移植性。JVM内存结构:与JVM运行时区域相关,定义了JVM如何分区存储不同类型的数据。3.2 happens-before与JMM的关系
JMM通过自行禁用处理器的若干重排序规则或者直接禁用某种类型的处理器来实现它向程序猿发出的happens-before几点保证,因为处理器的重排序规则在并发环境下可能会产生未知差错从而使得happens-before失去保证,所以必须要根据实际的运行情况进行适当的禁用。4.补充4.1 并发编程的三大重要特性原子性:一次操作或者多个操作要么全都执行,要么全都不执行。(这里不包括执行失败后回滚的情况,即并发编程的原子性 ≠ 事务性的原子性)可见性:当一个线程对某一个共享变量进行了修改,那么其他线程将能够立即看到修改后的新值。有序性:因为编译器和处理器对代码进行指令重排序的缘故,你所编写的代码的顺序不一定就是代码实际的执行顺序。参考文档JMM(Java 内存模型)详解
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。