线程的实现

线程是比进程更轻量级的调度执行单位,线程的引入可以把一个进程的资源分配和执行调度分开, 各个线程既可以共享进程资源(内存地址,I/O等),又可以独立调度。

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

内核线程的实现

内核线程(KLT)就是直接由操作系统内核支持的线程。这种线程由内核完成线程切换,由内核操纵调度器调度线程,并负责将线程的任务映射到处理器上。但是程序一般不直接使用内核线程,而是使用他的一种接口,轻量级进程(LWP),就是我们通常意义说的线程,每一个线程都有一个内核线程支持,这种轻量级进程与内核线程是1:1的关系。具体结构如下图所示:

image-20230325151710589

因为每个线程都有内核线程支持,可以当作一个独立的调度单元,即使其中一个被阻塞,也不影响整个进程继续工作。

局限性:由于是基于内核线程,所以线程的创建,同步等操作都需要系统调用。而系统调用需要从用户态切换为内核态。而且每个轻量级进程都需要一个内核线程支持,会消耗一定的内核资源。

用户线程实现

从广义上讲,一个线程只要不是内核线程,他就是用户线程的一种,从这里看,轻量级进程也属于用户线程。但轻量级线程始终需要建立在内核线程之上,并不具备通常意义上的线程。

而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及如何实现的。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。

用户线程没有内核线程的帮助,所以线程的创建、销毁、切换和调度都是用户必须考虑的问题,由于操作系统只把处理器资源分配到进程,所以阻塞如何处理,多处理器系统种如何将线程映射到其他处理器上这类问题解决起来将会异常困难,甚至有些是不可能实现的。

混合实现

即存在用户线程,也存在轻量级进程,用户线程还是建立在用户空间之中。而轻量级进程则作为用户线程和内核之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,这大大降低了整个进程被完全阻塞的风险。具体模型如下:

image-20230325161956615

一个CPU对应多个内核线程,然后每个内核线程对应着一个轻量级进程,而一个进程又对应了多个用户线程。

Java线程的实现

自jdk1.3起,主流java虚拟机的内存模型普遍被替换为基于操作系统原生线程模型来实现,即1:1的线程模型。以HotSpot为例,

它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以HotSpot自己是不会去干涉线程调度的(可以设置线程优先级给操作系统提供调度建议),全权交给底下的操作系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等,都是由操作系统完成的,也都是由操作系统全权决定的。

Java线程调度

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

协同式调度:线程的执行时间由自己控制,一个线程把自己的工作做完之后,要通知系统去切换到另一个进程去执行。优点是实现简单,而且不会有线程同步的问题,但是缺点也比较明显,线程执行时间不可控,如果一个线程一直不告知系统进行切换,那程序就会一直阻塞在那里。

抢占式调度:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。线程的执行时间是可控的,不会有一个线程阻塞导致整个系统崩溃的问题。

状态转换

java线程一共有6种状态,一个线程同一时间只能有其中的一种,而且可以通过特定语义进行转换。

1、新建(New):创建后尚未启动的线程处于这种状态。

2、运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间。

3、无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显式唤醒。以下方法会让线程陷入无限期的等待状态:

​ 1)没有设置Timeout参数的Object::wait()方法;

​ 2)没有设置Timeout参数的Thread::join()方法;

​ 3)LockSupport::park()方法。

4、限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:

​ 1)Thread::sleep()方法;

​ 2)设置了Timeout参数的Object::wait()方法;

​ 3)设置了Timeout参数的Thread::join()方法;

​ 4)LockSupport::parkNanos()方法;

​ 5)LockSupport::parkUntil()方法。

5、阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

6、结束(Terminated):已终止线程的线程状态,线程已经结束执行。

转换关系如下图所示:

image-20230325212241436

参考

《深入理解Java虚拟机》