jvm(5)-内存模型与线程

前言

随着硬件的发展,现在市面上的CPU基本都是多核多线程的CPU,而且计算机的运算速度与存储和通信子系统速度差距太大,导致大量时机都花费在磁盘I/O、网络通信、数据库访问上。如果不能很好的利用CPU资源,就会造成资源极大的浪费。为充分利用CPU资源,Java中引入高并发和多线程,为更好的使用这些利器,我们需要在JVM层面了解内存模型以及线程实现方式。

Java内存模型

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储在内存中和将变量从内存中取出这样的底层细节。这里变量指非线程私有的变量(局部变量),而是指线程共享的变量例如静态字段、实例字段、数组等。

主内存指虚拟机中内存的一部分,每条线程拥有自己的工作内存,工作 内存中保存主内存变量的副本,线程对变量的操作都在工作内存中进行,不能直接读写主内存的变量。

工作内存、主内存与Java内存管理没有直接关系,划分的层次不一样。如果强行对照,主内存主要对应于堆内存中的实例数据部分;工作内存对应虚拟机栈的部分区域。

图1内存模型

交互操作

  • lock:作用于主内存变量,线程独占

  • unlock:作用于主内存变量。锁定释放

  • read:作用于主内存变量,将变量值从主内存读到工作内存
  • load:作用于工作内存变量,将read值载入工作变量副本
  • use:作用于工作内存变量,值传递执行引擎,供后面字节码调用
  • assign:作用于工作内存变量,从执行引擎取值,赋给工作内存变量
  • store:作用于工作内存变量,将工作内存变量传递主内存
  • write:作用于主内存变量,将store值写入主内存变量

上面每个操作都具有原子性,且read与load、use与assign、store与write必须成对出现不允许单独使用。

Java与线程

进程独立拥有CPU、内存、硬盘等硬件资源,切换进程开销比较大,是比较重的调度执行单位。而线程作为轻量级的调度执行单位,比进程更加易于管理。资源分配的基本单位是进程,但CPU调度的基本单位是线程。

线程实现当时主要有三种:内核线程实现、用户线程实现、用户线程加轻量级进程混合实现。

内核线程实现

内核线程,KLT(Kernel Level Thread),直接由操作系统内核支撑的线程。线程切换由内核完成,无需用户负责,通过调度器进行调度将内核线程任务映射到CPU上。LWP(Light Weight Process)是指轻量级进程,就是我们平常所讲的线程。

这种实现方式线程与内核线程之间的关系是1:1,每个线程都由一个内核线程支撑。缺点是内核线程数量有限导致轻量级进程数量有限,且线程切换由内核态完成,故线程切换时需要用户态与内核态相互转换,会带来额外的开销。优点是实现方式简单,用户无需自己实现线程的切换代码,并且其中一个线程阻塞不会影响其他线程。

图2内核线程实现

用户线程实现

用户线程,UT(User Thread)广义上指除内核线程以外的线程。狭义上指建立在用户空间上的线程,系统内核无法感知。用户线程的建立、同步、销毁和调度都在用户态中完成。

这种实现方式中,进程与用户线程的关系为1:N。优点是用户线程实现不依赖于系统内核,数量不受内核线程数量的限制(理论上)。缺点是由于不依赖于系统内核,线程的切换、调度、销毁、同步等都需要用户自己去实现,代码很复杂。

图3 用户线程实现

用户线程加轻量级进程混合实现

能不能结合上两者的优点?线程创建、切换、调度和销毁等操作交由内核实现,但数量又不受内核线程数量限制?

混合实现就是针对上面两种类型的优点做整合,轻量级线程仍由内核线程支持,但是在用户空间仍然使用用户线程,这样用户线程与轻量级线程之间的关系为N:M 。

图 4 用户线程与轻量级进程混合实现

状态转换

参考

《深入理解java虚拟机》