操作系统中,一个进程往往代表着一个应用程序实例,而线程是进程中轻量级的调度单元,也可以看作是轻量级的进程,可以共享进程资源。下面简单介绍在操作系统中线程通用实现方式。

线程实现模型

暂且抛开Java线程,先说明一下在操作系统中,线程通用的几种实现方式。实现线程主要有三种方式。

内核线程模型

使用内核线程实现的方式,通常也被成为1 : 1实现模型。内核线程(Kernel Level Thread,KLT)是直接由操作系统内核来支持的线程,这种线程由内核来控制切换,内核通过调度器(Scheduler)来对线程进行调度,并负责将线程任务映射到各个处理器,在多核操作系统中具有能力并行处理多个任务,这种支持多线程的内核被称为多线程内核(Mutil-Threads Kernel)。
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口(轻量级进程Light Weight Process)LWP,也就是通常意义所描述的线程,每个轻量级进程都由一个内核支持,因此先支持内核线程,才能有轻量级进程。其中KLT、Schedule、LWP之间关系如下图所示:
未命名文件 (5).svg
在这个模型中,每个轻量级进程都是一个调度单元,单个KLT的阻塞不会影响其他单元的调度,当然也有其本身的局限性,由于是基于内核线程进行创建的,所以线程的各种操作,如创建、同步等都需要进行系统调用。应用程序运行在用户空间,内核处于用户态(User Mode),系统调用需要进行用户态和内核态(Kernel Mode)切换,频繁的进行用户态内核态切换,会严重消耗系统性能。此外,每个KLT都需要内核来支持,因此也会消耗内核资源如内核线程空间等,因此这种模型下所能创建的线程数也是很有限的。

用户线程模型

使用用户线程实现的方式被称为1 : N模型,非内核线程都可以被看作是用户线程(User Thread,UT)的一种。这里需要辩证的看待关于用户线程的定义,广义上来从非内核线程的角度看,轻量级进程也属于用户线程的范畴,但是由于其建立在内核基础之上,过于依赖系统调用,又不具备通常意义上用户线程的优点。其实现模型如图所示:
未命名文件 (6).svg
狭义上所说的用户线程指完全建立在用户空间的线程,线程的控制无需内核参与,内核也无法感知其实现模式,这种线程也不需要进行用户态和内核态的切换,因此用户线程对资源的使用率较小,支持大规模的线程数量。部分高性能数据库中的线程就是完全由这种类型的用户线程来实现。
用户线程的优势也是其劣势,由于没有内核的支持,所有线程都需要由用户程序处理。操作系统通常只会进行进程级别资源的分配,那么在用户空间,用户线程如何创建、销毁、切换、阻塞以及调度都要由用户来处理,因此用户线程实现的程序也提高了复杂度和故障机率,除非是大规模的线程需要场景下,否则一般不倾向于使用用户线程。
Java中的线程曾经使用用户线程实现,最终由于其复杂度被放弃。近年来,以高并发为特性的语言,如Golang、Erlang中使用用户线程的场景有所提升。

混合模型

基于使用内核线程和用户线程的模型,还有一种就是整合两种模型特性的混合实现模型,也被成为M : N模型。这种模型中即使用了内核线程,也使用了用户线程,通过轻量级进程作为用户线程和内核线程之间的桥梁,既可以使用内核提供的线程进行调度及处理器映射,同时也兼具了大规模用户线程并发。这种模型下用户线程和轻量级进程的数量比不固定,如下图所示:
未命名文件 (7).svg
在许多UNIX类操作系统,如Solaris、HP-UX等都提供了这种M : N的线程模型实现。

Java线程实现

实现模型

Java虚拟机规范中并没有定义关于线程如何实现,因此不同虚拟机厂商实现方式也没有统一标准。在JDK1.2前的线程,早期的Classic虚拟机中,基于一种被称为绿色线程的用户线程来实现。但在JDK1.3之后,商用Java虚拟机普遍开始使用内核线程模型,即1 : 1模型来作为Java线程的实现。
在HotSpot虚拟机中,每个Java线程都是直接映射到操作系统的原生线程来实现,虚拟机本身并不会干预线程的创建、调度,但是可以设置线程的优先级给予操作系统建议。关于线程的冻结或者唤醒、线程由那颗CPU核心执行以及可使用的CPU执行时间片都由操作系统来完成。
当然也有特殊场景,用于JavaME的CLDC HotSpot Implementation同时支持1 : N以及特殊的混合模型。Solaris平台的HotSpot,由于操作系统的特性,因此同时支持1 : 1和N : M的两种线程模型,通过虚拟机参数设置。
总体来说,Java线程模型的实现通常依赖于所运行的操作系统内核提供的支持,在不同平台并不能达成一致,因此Java虚拟机规范也未明确的做出定义。另外,至于使用何种线程模型,其所影响的只有线程规模(线程数量)和操作成本。

调度策略

线程无论基于何种模型创建,都有其调度策略,线程的调度指的是操作系统为线程分配使用权的过程。通常调度方式包含两种,分别是协同式(Cooperative Threads Scheduling)和抢占式(Preemptive Threads Scheduling):

  1. 协同式调度

使用协同式调度方式的线程调度由其本身来控制,线程在自身工作执行完成后,主动通知系统切换到另一个线程执行,这种方式实现简单,便于控制。但是过于依赖线程本身来控制调度,如果某个线程执行任务的程序存在问题就会导致一直阻塞。

  1. 抢占式调度

使用抢占式调度方式的多线程系统,线程的调度由系统分配执行时间,线程的切换由系统决定。这种调度方式下,线程的执行时间可控,不会因单个线程问题导致应用程序堵塞。Java中所使用的线程调度策略就是抢占式,虽然整个调度基于系统来确定,但是可以通过设置优先级的方式给予操作系统一定的建议,总共包含1~10优先级,优先级越高,线程越容易被选择执行。