Java内存模型与Java线程的实现原理
Posted nogos
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java内存模型与Java线程的实现原理相关的知识,希望对你有一定的参考价值。
硬件的效率与一致性
![](https://image.cha138.com/20220218/edfa99b29f7c4079a9fefb0bd0c7e49a.jpg)
Java内存模型
Java虚拟机规范中试图定义一种 Java内存模型来 屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台上都能达到 一致的内存访问效果。主内存与工作内存
Java内存模型的主要目标:定义程序中各个 变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。注意,此处的变量与Java编程语言中所说的变量有所区别,它包括实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。 为了获取较好的执行效能,Java内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化操作。 Java内存模型规定了所有的变量都存储在 主内存中,此处的主内存仅仅是虚拟机内存的一部分,而虚拟机内存也仅仅是计算机物理内存的一部分(为虚拟机进程分配的那一部分)。 每条线程还有自己的 工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(读取、赋值),都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者之间的交互关系如下图:![](https://image.cha138.com/20220218/a77c478126694b4f8e23502f40b6a461.jpg)
内存间的交互操作
关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存等的细节,Java内存模型定义了8种操作来完成,虚拟机实现时必须保证下面提及的每一种操作都是原子操作。
lock(锁定):作用于主内存的变量,它把一个变量标志为一条线程独占的状态。
unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接受到的值赋给工作内存的变量。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
write(写入):作用于主内存中的变量,它把store操作从主内存中得到的变量值放入主内存的变量中。
![](https://image.cha138.com/20220218/22513f30722d48659cc7486b861e9d47.jpg)
Java与线程
线程的实现
我们知道,线程是比进程更轻量级的 调度执行单位,线程的引入,可以把一个进程的 资源分配和 执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度(线程是CPU调度的基本单位)。 主流的操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程操作的统一处理。Thread类中所有关键方法都是声明为Native的,在Java API中, 本地方法往往意味着这个方法没有使用或无法使用平台无关的手段来实现(当然也有可能是为了执行效率而使用Native方法)。 正因如此,本节标题为“线程的实现”而不是“Java线程的实现” 线程的实现主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。1、使用内核线程实现
内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作系统调度器对线程进行调度,并负责将线程的任务 映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫多线程内核。 程序一般不会直接去使用内核线程,而是使用内核线程的一种高级接口—— 轻量级进程(Light Weight Process,LWP),由于每个轻量级进程都由一个内核支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程和内核线程之间1:1的关系称为一对一线程模型。![](https://image.cha138.com/20220218/942e8c93f3e740e98e1b4a26417c0863.jpg)
2、使用用户线程实现
从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程,因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都进行系统调用,效率会受到限制。 而 狭义上的用户线程指的是完全建立在用 户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的创建、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。这种线程不需要切换到内核态,因此操作可以是非常快速且非常低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。![](https://image.cha138.com/20220218/d6463f4cc2ff4eb49839f8110fd1450f.jpg)
3、使用用户线程加轻量级进程混合实现
在这种混合模式下,即存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核线程提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,大大降低了整个进程被完全阻塞的风险。这种混合模式,用户线程与轻量级进程的数量比是不定的,即N:M的关系,也称为多对多的线程模型。![](https://image.cha138.com/20220218/b83868bd6964464eaed3c4a10b8cd490.jpg)
Java线程的实现
Java线程在JDK 1.2之前是基于用户线程实现的,而JDK 1.2中,线程模型替换为 基于操作系统 原生线程来实现。 因此,在目前的JDK版本中,操作系统支持怎样的线程模型,很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没有达成一致,虚拟机规范中也没有限定Java线程需要使用哪种线程模型来实现。 线程模型只是对线程的并发规模和操作成本产生影响,对Java程序的编码和运行来说,这些差异都是透明的。 对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。Java线程调度
Java的线程调度方式是抢占式调度,虽然Java线程的调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置 优先级来完成。 不过,线程的优先级并不是太靠谱,因为Java线程是通过映射到原生线程上来实现的,所以线程调度最终还是取决于操作系统,虽然现在很多操作系统都提供了优先级的概念,但是并不见得与Java线程的优先级一一对应。例如Windows中就只有7种线程优先级,而Java语言一共设置了10个级别的线程优先级。![](https://image.cha138.com/20220218/9cf4f40df9db444bb1e558298af6725a.jpg)
Java线程的状态转化
新建(New):创建后尚未启动的线程处于这种状态。 运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是说处于此种状态的线程可能正在执行,也可能正在等待CPU为它分配执行时间。 无限期等待(Waiting):处于这种状态下的线程不会被分配CPU执行时间,他们要等待被其他线程显示唤醒。 限期等待(Timed Waiting):处于这种状态下的线程也不会被分配CPU执行时间,不过无须等待被其他线程显示唤醒,在一定时间之后它们由系统自动唤醒。 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待着获取一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生。 结束(Terminate):已经终止的线程的线程状态,线程已经结束执行。![](https://image.cha138.com/20220218/c92c97c2fe23469e893c86e949fde956.jpg)
《深入理解JVM虚拟机》
以上是关于Java内存模型与Java线程的实现原理的主要内容,如果未能解决你的问题,请参考以下文章