JVM的线程是建立在操作系统基础上的,要了解JVM的线程模型要先了解操作系统的线程模型。
前面讲解过,线程是比进程更轻量级的调度执行单位。线程可以把一个进程的资源分配和执行分开,各个线程间既可以共享进程资源(内存地址、文件I/O等),也可以独立调度执行。
主流的操作系统都提供了线程实现,各个操作系统的实现方式各有不同,但在实现模式上主要有3种方式:内核线程实现(1:1实现)、用户线程实现(1: N 实现)、混合实现( N : M 实现)。
1.内核线程实现
内核线程(Kernel-Level Thread,KLT)完全由操作系统内核来完成线程调度。内核通过系统任务调度器(Scheduler)对各个内核线程进行统一调度,并负责将线程的任务映射到各个CPU处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有同时处理多个任务的能力。支持多线程的内核就称为多线程内核(Multi-Threads Kernel)。应用程序一般无法直接使用内核线程来处理任务,而是使用内核线程的接口、轻量级进程(Light Weight Process,LWP)来处理任务,通常把这种轻量级进程称为线程。
由于每个轻量级进程都需要一个内核线程的支持,因此操作系统只有先支持内核线程,才能实现轻量级进程。轻量级进程与内核线程之间是1:1的对应关系。内核线程模式也称为一对一的线程模型,如图2-11所示。
图2-11 内核线程模型
模式优点 :利用系统内存线程,应用程序可以同时实现多个任务的调度,能够极大地提高程序的并发度与执行效率。线程的调度完全由系统内核来完成,并且操作系统提供了丰富的线程接口。应用程序层只用调用线程的线程操作接口,不用关注底层的实现就能完成线程的执行。
模式缺陷 :因为是基于内核线程实现的,所以线程的各种操作,例如创建、执行及同步都需要进行系统内核调用。而系统内核调用需要在用户态和内核态中来回切换,运行的代价较高。每个线程都需要有一个内核线程的支持,因此线程要消耗一定的内核资源。
2.用户线程实现
从系统角度上来看,线程只有两种分类:内核线程(Kernel-Level Thread,KLT)与用户线程(User-Level Thread,ULT)。用户线程是指不需要内核支持而在用户程序中实现的线程,线程的实现不依赖于操作系统核心的支持。应用程序使用用户线程库提供的函数来创建、同步、调度和管理用户线程,并且不需要内核的支持。应用程序需要实现多个线程之间的调度与同步,同时需要实现软件上下文与硬件上下文的切换。这种进程与用户线程之间1: N 的关系称为一对多的线程模型,如图2-12所示。
图2-12 用户线程模型
模式优点 :线程调度与执行不需要从用户态切换到内核态,因此调度非常快速并且性能消耗非常低。同时,因为不需要内核的支持,所以应用程序也能够创建更多的线程。
模式缺陷 :所有的线程操作都需要由用户程序自己去处理,导致整个线程的实现非常复杂,实现的难度也非常大。除非在特定场景下有明确需求,一般应用程序都不会使用用户线程。
3.混合实现
混合实现是将内核线程与用户线程一起使用的实现方式,也被称为 N : M 实现模型,如图2-13所示。在混合模式下,应用层的任务调度通过用户线程实现,底层任务的执行采用轻量级进程实现。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、销毁等操作非常高效,并且可以支持大规模的用户并发线程。而轻量级进程作为用户线程和内核线程之间衔接的桥梁,这样可以使用内核提供的线程调度及处理器映射功能,并且用户线程的系统调用要通过轻量级进程来完成,这样大大降低了整个进程被完全阻塞的风险。在这种混合模式下,用户线程与轻量级进程的数量比是不确定的,是 N : M 的关系。
模式优点 :混合模式结合了内核模式与用户模式的优点,相同的内核资源能够支持更多的并发线程,在调度管理上减少了一部分任务调度带来的负载。
模式缺陷 :因为整个混合模式的设计与实现涉及应用程序层与内核层的协调和调度,所以整个模式的实现会比较复杂。
由于早期的操作系统无法提供很好的内核线程支持,因此JVM基于“绿色线程”(Green Thread)概念实现了用户线程。现在,JVM的线程普遍都是采用内核线程来实现,也就是上面说的1:1的线程模型。
图2-13 混合实现模型
以HotSpot为例,线程是通过操作系统原生线程来实现的,所以线程的调度会全权交给底层的操作系统去处理,HotSpot无法干涉线程调度(可以在创建时设置线程优先级,给操作系统提供调度建议)。何时冻结或唤醒线程,该给线程分配多少处理器执行时间,该把线程安排给哪个处理器核心去执行等,都是由系统内核决定的。
在Linux系统上,内核线程是通过Phtread线程库来实现的。Phtread提供了线程创建、同步、取消、销毁等详细的内核线程操作方法,以及丰富的互斥量、条件变量、信号量等系统内核线程调度管理功能。所以,Linux操作系统上的JVM是完全依赖Phtread线程库的函数功能来实现的,线程模型如图2-14所示。例如,线程的创建依赖pthread_create函数实现的,线程的等待由pthread_cond_wait和pthread_cond_timedwait函数来实现。
图2-14 JVM线程模型