总结:对Java内存模型JMM的理解

 2019-12-10 16:18  阅读(1184)
文章分类:Java Core

JMM规定了线程的工作内存和主内存的交互关系,以及线程之间的可见性和程序的执行顺序。一方面,要为程序员提供足够强的内存可见性保证;另一方面,对编译器和处理器的限制要尽可能地放松。JMM对程序员屏蔽了CPU以及OS内存的使用问题,能够使程序在不同的CPU和OS内存上都能够达到预期的效果。

在jsr-133中是这么定义的

A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program. For the Java programming language, the memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.

也就是说一个内存模型描述了一个给定的程序和和它的执行路径是否一个合法的执行路径。对于java序言来说,内存模型通过考察在程序执行路径中每一个读操作,根据特定的规则,检查写操作对应的读操作是否能是有效的。

个人的理解就是:Java 的编译器和CPU都可以对程序进行优化重排序,但是要遵循一些规则,不能随意的重排序,例如 volatile,final,以及在释放和获取锁时程序执行的顺序,为多线程打下了基础。同事还规定了

JMM中的模型规则

两个比较重要的模型:
**Sequential Consistency Memory Model:顺序一致性模型。**如果程序是正确同步的,程序的执行将具有顺序一致性(Sequentially Consistent)——即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。
第一:一个线程中的所有操作必须按照程序的顺序来执行;
第二:(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内
存模型中,每个操作都必须原子执行且立刻对所有线程可见

20191210001726\_1.png

正真做到上面提到的顺序一致,每一个线程操作的结果在内存中立即对另一个线程可见,会导致程序运行性能的下降,例如,T1线程的每一次写操作都要写回内存,要确保对T2线程可见,程序的执行效率相当低。所以JMM并没有严格要求程序满足该模型,只是能够确保在程序正确只用了锁的情况下,能够确保程序在宏观上是顺序一致的。例如,加锁和解锁是顺序一致的。

Happens-Before Memory Model : 先行发生模型。happens-before是JMM最核心的概念。

int a = 0;(1)
    int b = 0;(2)
    int c = a+b;(3)

我们说,1 happen brfore 2,2 happen before 3,根据传递性规则,1happen before3。

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。

第一个规则是为程序员提供的保证,在同一个线程内,前面的操作结果对后面的程序可见。
第二个规则是向CPU提出的要求,在保证执行结果相同的情况下,可以做程序的重排序,来优化程序的运行。

《JSR-133:Java Memory Model and Thread Specification》定义了如下happens-before规则。
1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
6)join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

1),2),4),5),6)都比较容易理解,3)准备在写一篇blog来说明。

JMM规定的主存和工作内存的交互

JMM规定了下面8种操作在虚拟机实现时保证每一种操作都是原子的不可分的。(long和double 个别操作除外),lock、unlock、read、load、use、assign、store、write。

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

这8种内存访问操作很繁琐,后文会使用一个等效判断原则,即先行发生(happens-before)原则来确定一个内存访问在并发环境下是否安全。

总之,JMM规定了程序员怎么和JVM的内存打交道,JVM的内存怎么和OS内存打交道,主内存怎么和工作内存打交道。并规定了一些确保程序正确高效运行的规则。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 总结:对Java内存模型JMM的理解

相关推荐