JVM-内存模型与线程

硬件效率与一致性

CPU在处理任务的时候经常需要用到各种数据,这些数据来自内存,但是内存的IO速度和CPU的计算速度有着几个数量级的差距,如果不进行优化就无法高效的利用好CPU。
为了解决这个问题,现代计算机在内存和CPU之间增加了一层高速缓存,这些CPU就可以高速的读写数据而不用等待缓慢的内存读写。

缓存一致性

缓存一致性问题,在多路处理器系统中,每一路处理器都有自己的高速缓存,他们共用同一主内存。当多路处理器涉及同一主内存区域的操作时,就会有缓存一致性的问题,这种情况下,要同步给主内存的数据到底应该以谁的数据为准呢?为了解决一致性的问题,又加入了一致性协议,遵循一致性协议来操作才能保证一致性(一致性协议有MSI、MESI、MOSI等)。
在这里插入图片描述

乱序执行优化

除了高速缓存优化性能,处理器在执行指令的时候,还会用到乱序执行优化的方法,处理器保证执行结果正确性,但是不保证执行指令的顺序。

Java内存模型

Java内存模型(Java Memory Model,JMM)是为屏蔽各种硬件和操作系统的内存访问差异提出的,通过JMM实现让Java程序在各种平台都能达到一致的内存访问效果。

主内存与工作内存

Java内存模型规定所有的变量都存储在主内存中,这里的主内存与物理模型的主内存可以类比,但是本质上来说它只是虚拟机内存的一部分。线程的工作内存中保存了该线程使用到的变量的主内存副本,线程不能直接在主内存上进行读写,所有的操作都要在自己的工作内存上进行。
在这里插入图片描述
Java内存模型的主内存和工作内存与内存区域的堆栈并不是同一层次的划分,Java内存模型应该更对应基础的物理硬件,主内存对应物理内存,工作内存则对应寄存器和高速缓存。如果非要和JVM内存区域扯上关系,那么主内存主要对应Java堆,工作内存对应虚拟机栈。

内存间的交互操作

  1. lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态;
  2. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
  3. read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便后续的load动作使用;
  4. load(载入):作用于主内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中;
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量传递给执行引擎;
  6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量;
  7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write操作;
  8. write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
    这里有read和load、store和write的区别问题,从字面上两组操作更加非常的相似,我是怎么理解的,存储到高速缓存中的数据其实还要放到寄存器中才能被CPU使用,read是从主内存读取到高速缓存中,store从高速缓存读取到寄存器中。

交互规则

  1. 如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序的执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  2. 不允许read和load、store和write操作之一单独出现。
  3. 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  4. 不允许一个线程无原因的(没有发生过任何assign操作)把数据从工作内存同步回主内存。
  5. 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  6. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。
  7. 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
  8. 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许unlock一个被其他线程锁定的变量。
  9. 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

double和long非原子性协议

Java规则八种内存间的交互操作都必须是原子性的,但是对于64位的数据又有比较宽松的规定,在没有volatile修饰的情况,允许把64位的数据分成两次32位进行处理,这对于32位的虚拟机来说可能存在问题,这就是double和long非原子性协议。

原子性、可见性和有序性

  1. 原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  2. 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  3. 有序性:程序执行的顺序按照代码的先后顺序执行。

AS-IS-SERIAL

as-if-serial语义要求不管指令如何重排序,程序的执行结果在单线程的时候来看都不会改变。编译器、runtime和处理器都必须遵守as-if-serial语义。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

并行发生原则

JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。

具体定义

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

具体的规则

如果两个操作之间的关系不在以下规则,或者无法由以下规则推导出来,则它们没有顺序性保证,虚拟机可以对它们随意地进行重排序。

  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()操作成功返回。
  7. 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
  8. 对象finalize规则:一个对象的初始化完成先行于发生它的finalize()方法的开始。

Java线程

线程的实现

线程的实现有三种方法:使用内核线程实现(1:1),使用用户线程实现(1:N),使用用户线程加轻量级进程混合实现(N:M)。

内核线程实现

Java现在的实现方式,内核线程(KLT)是由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。程序一般不会直接使用内核线程,而是使用内核线程的高级接口,轻量级进程(LWP),轻量级进程才是我们通常意义讲的线程,它和内核线程有一对一的关系。
在这里插入图片描述
缺点:

  1. 由系统调度,需要在用户态和内核态来回切换,代价相对较高。
  2. 消耗内核资源,系统支持的轻量级进程的数量是有限制的。

用户线程

一个线程只要不是内核线程,都可以认为是用户线程(UT),原则上来说轻量级进程也属于用户线程,当轻量级进程始终是建立在内核之上的,这样就有较高的代价。
用户线程实现的方式是指用户线程的创建,同步,调度,销毁等操作全部在用户态中进行,内核态不会有任何感应。
在这里插入图片描述

混合实现

内核线程的实现和用户线程的实现都各自有着缺点,混合实现的方式可以结合两种实现方式的优点。
在这里插入图片描述

线程状态切换

  1. 新建(NEW):新建(new)但是没有启动(start)的线程。
  2. 运行(RUNNABLE):已经启动的线程,此状态的线程在等待操作系统给他分配时间片。
  3. 运行中(RUNNING):正在运行的线程。
  4. 无限期等待(WAITING):线程进入等待状态,不会被分配时间,等待被其他线程唤醒,进入RUNNABLE状态。
  5. 限期等待(TIMED WAITING):线程进入等待状态,不会被分配时间,等待被其他线程唤醒,或者到时自动进入RUNNABLE状态。
  6. 阻塞(BLOCKED):线程等待获取一个排它锁。阻塞:等待获取一个排它锁,另一个线程释放排它锁会触发这个事件;等待:等待一段时间,或者唤醒动作的发生。
  7. 结束(TERMINATED):已终止的线程状态,结束运行。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值