Java内存模型简单讲解

       在实际讲解并发与高并发之前我们还需要先学习一下Java虚拟机是怎么解决这些问题的;为了屏蔽掉硬件以及各种操作系统的内存访问差异以实现让java程序在各个平台下都能达到一致的并发效果,Java虚拟机规范中定义了Java内存模型(Java Memory model,简称JMM).个人比较推荐《深入理解JVM》中的关于JMM的讲解。很详细了。书中的理论知识,准备面试足够了。

1.JMM基本概念

       Java内存模型是一种规范,规范了Java虚拟机与系统内存之间是如何协同工作的,它规定了一个线程如何和何时可以看到其他线程修改过的共享变量的值以及在必要的时候如何同步的访问共享变量;在明确了Java内存模型是做什么的之后我们具体的来介绍一下Java内存模型.

2.JMM讲解

     首先图片中内存分配的概念。一个是堆heap,一个是栈stack

      右边的小图对应的是cpu和主存的一种信息交互的策略,因为二者速度相差太大,所以在现代计算机中加入了cache-主存映射机制。具体的在大学计算机组成原理课本上有详细的讲解。

2.1 Java里的堆-Heap

      这里需要跟数据结构中的堆做一下区分。不要学混了哈。Java里面的堆是一个运行的数据区,堆是由垃圾回收来负责的,堆的优势是可以动态分配内存大小,生命期也不必事先告诉编译器,因为她是在运行时动态分配内存的,Java的垃圾回收器会自动的收走这些不再使用的数据,但是堆也有缺点,由于要在运行时动态分配内存,因此对的存取速度会相对来说慢一些。

2.2Java里的栈-stack

       栈的优势是存取速度比堆要快,仅次于计算机里的寄存器,栈的数据是可以共享的;但是它的缺点也比较明显,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性,栈中主要存在一些基本类型的变量(小写的int,short,long等基本数据类型)

       JMM要求调用栈和本地变量存放在线程栈上,对象存放在堆上。(注:数组是一个对象)这里需要具体说一下,一个本地变量也有可能是指向本地的引用,这种情况下引用本地变量是存在线程栈上的,但是对象本身是存放在堆上。一个对象它可能包含方法(上图中的methodOne和methodTwo)这些方法可能包含本地变量(上图中的local variable)这些本地变量仍然是存放在线程栈上的,即使这些方法所处的对象存放在堆上。

    不管这个成员变量式原始类型还是引用类型,静态成员变量跟随着类的定义一起存放在堆上,堆上的对象可以被所持有这个对象引用的线程访问,当一个线程可以访问这个对象的时候,它也可以访问这个对象的成员变量,如果两个线程同时访问堆上的同一个对象,他们将会都访问这个对象的成员变量,但是每一个线程都拥有了这个对象成员变量的私有拷贝。

3.JMM与计算机硬件的关联

通过上图,我们可以看出,JMM与实际的计算机硬件架构有一定差异,硬件架构没有区分线程栈和堆,因为对于硬件架构来说,所有的堆和栈都分布在主内存里面,部分栈和堆在某些事件可能会出现在CPU缓存和CPU内部的寄存器里。

3.1JMM抽象架构

线程之间共享变量存储在主内存里面,每个线程都有一个私有的本地内存,本地内存是Java内存模型的抽象概念(并不是真实存在的,它包含缓存,写缓存区,寄存器以及其他的硬件和编译器的优化),本地内存中它存储了该线程以读或写共享变量拷贝的一个副本,例如上图中,如果线程A要使用共享变量,它会先拷贝出共享变量的一个副本放在自己的本地内存中,从更低的层次来说,主内存就是硬件的内存,是为了获取更好的运行速度,虚拟机以及硬件系统可能会让工作内存有限存储于寄存器或高速缓存中。Java内存模型中的工作内存是

寄存器和高速缓存的一个抽象描述,而JVM的静态内存存储模型它只是对内存的一种物理划分,它只局限在内存局限在JVM的内存,现在如果线程A和线程B要进行通信必须要经历两个步骤:第一步线程A需要将本地内存中更新过的共享变量刷新到主内存中,第二步线程B从主内存中读取之前线程A刷新到主内存中的共享变量.

下面简述一个多线程中常见的问题。

①我们假设主内存中当前变量的值为1,此时线程A和线程B同时开始执行;

②线程A从主内存中拿到的值为1,然后存储到自己的本地内存A中最后执行add的操作也就是+1的动作;

③线程A将计算后的结果(此时的结果已经变成2)通过本地内存重新写回到主内存中;

④线程B此时拿到的值也可能是1,然后将这个值放入自己的本地内存中最后执行加1的操作,最后将计算后的值重新写入到主内存;

⑤线程A与线程B同时将新的结果写入到主内存中,而不是有序的交替读取-计算-写入;

⑥因为在程序执行期间两个线程之间的数据是互相不可见的,因此就计数就出现了问题;

原因已经分析过了,这个时候我们就需要增加一些同步的手段增加程序运行期间并发执行的准确性;

4.JMM中同步的八种操作

①lock(锁定) : 作用于主内存的变量,把一个变量标识为一条线程独占的状态;

②unlock(解锁) : 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量可以被其他线程锁定;

③read(读取) : 作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用;

④load(载入) : 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中;

⑤use(使用) : 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎;

⑥assign(赋值) : 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量;

⑦store(存储) : 作用于工作内存的变量,把工作内存中的一个变量值传送到主内存中,以便随后的write的操作;

⑧write(写入) : 作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中;

5.JMM同步规则

①不允许read和load、store和write单独出现。即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内从发起会写了但主内存不接受的情况出现。

②不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步到主内存里。

③不允许一个线程无原因地把数据从线程工作内存同步回主内存中,即没有发生过任何的assign操作就同步到主内存中。

④一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个没有被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行assign和load操作。

⑤一个变量在同一时刻,只允许一个线程对其进行loack操作,但lock操作可以被同一个线程重复执行多次,多次执行lock后,只有执行相同次数的unloack操作,变量才能被解锁。

⑥如果对一个变量执行了lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

⑦如果一个变量事先没有被lock操作锁定,那就不允许对他执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量

⑧对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

图解:

总结:1.JMM是一个规范,它规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值以及在必要时如何同步的访问共享变量,它要求调用栈和本地变量存放在线程栈上,对象存放在堆上。

2.线程间的通信必须要经过主内存。

3.顶一个了同步的八种操作和八种基本规则。(具体参考《深入理解JVM》)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值