java笔记JVM(java虚拟机)之内存模型和线程

Posted 棉花糖灬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java笔记JVM(java虚拟机)之内存模型和线程相关的知识,希望对你有一定的参考价值。

1. Java内存模型与线程

(1) Java内存模型

Java内存模型用来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果,目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。

Java内存模型把内存划分为主内存(类似于物理内存)和工作内存(类似于Cache),规定了所有的变量都存储在主内存中;线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

主内存、工作内存与Java堆、栈、方法区等并不是同一个层次的对内存的划分,这两者基本上是没有任何关系的。主内存主要对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更基础的层次上说,主内存直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机(或者是硬件、操作系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问的是工作内存

(2) 内存间交互操作

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

(3) Java内存模型必须满足的规则

不允许read和load、store和write操作之一单独出现;不允许一个线程丢弃它最近的assign操作;不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中;一个新的变量只能在主内存中“诞生”;一个变量在同一个时刻只允许一条线程对其进行lock操作;如果对一个变量执行lock操作,那将会清空工作内存中此变量的值;如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作;对一个变量执行unlock操作之前,必须先把此变量同步回主内存中

(4) 对于volatile型变量的特殊规则

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制

当一个变量被定义成volatile之后,它将具备两项特性:第一项是保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量并不能做到这一点,普通变量的值在线程间传递时均需要通过主内存来完成。volatile变量的运算在并发下一样是不安全的。使用volatile变量的第二个语义是禁止指令重排序优化,普通的变量仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。

(5) 针对long和double型变量的特殊规则

允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这四个操作的原子性,这就是所谓的“long和double的非原子性协定”

(6) 原子性、可见性与有序性

  • 原子性:操作执行过程中不允许被打断
  • 可见性:可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改,除了volatile之外,Java还有两个关键字能实现可见性,它们是synchronized和final
  • 有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的

(7) 先行发生原则

先行发生是Java内存模型中定义的两项操作之间的偏序关系,比如说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到

(8) 线程的实现

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

  • 内核线程实现:内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上
  • 用户线程实现:狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及如何实现的
  • 混合实现:既存在用户线程,也存在轻量级进程

(9) Java线程调度

线程调度是指系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式(Cooperative Threads-Scheduling)线程调度和抢占式(Preemptive Threads-Scheduling)线程调度。

如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上去。优点实现简单,切换操作对线程自己是可知的;缺点线程执行时间不可控。如果使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定

(10) 线程状态转换:

  • 新建(New)
  • 运行(Runnable)
  • 无限期等待(Waiting)
  • 限期等待(Timed Waiting):
  • 阻塞(Blocked)
  • 结束(Terminated)

(11) 协程和纤程

协程的主要优势是轻量,无论是有栈协程还是无栈协程,都要比传统内核线程要轻量得多。对于有栈协程,有一种特例实现名为纤程(Fiber)

以上是关于java笔记JVM(java虚拟机)之内存模型和线程的主要内容,如果未能解决你的问题,请参考以下文章

深入理解JVM虚拟机读书笔记——内存模型与线程

深入理解JVM虚拟机读书笔记——内存模型与线程

java笔记JVM(java虚拟机)之垃圾收集与内存分配策略

深入理解 Java 虚拟机之学习笔记

JVM学习笔记四:运行时数据区之虚拟机栈

Java虚拟机之内存模型