【转载】JUC常见面试题:Java线程和操作系统线程有什么区别
整理来自于:https://cloud.tencent.com/developer/article/1818151
1 操作系统的用户态和核心态
在操作系统中,内存通常会被分成用户空间(User space)
与内核空间(Kernel space)
这两个部分。当进程/线程运行在用户空间时就处于用户态,运行在内核空间时就处于内核态:
- 运行在内核态的程序可以访问用户空间和内核空间,或者说它可以访问计算机的任何资源,不受限制,为所欲为,例如协调 CPU 资源,分配内存资源,提供稳定的环境供应用程序运行等
- 而应用程序基本都是运行在用户态的,或者说用户态就是提供应用程序运行的空间。运行在用户态的程序只能访问用户空间
区分用户态和核心态的原因,是为了让那些比较危险的操作需要切到内核态才能运行,比如 CPU、内存、设备等资源管理器程序就应该在内核态运行,否则安全性没有保证。用户态的程序不能随意操作内核地址空间,这样有效地防止了操作系统程序受到应用程序的侵害。
而如果用户态的程序想要访问内核空间,就需要进行系统调用从用户态切换到核心态
2 操作系统线程
操作系统中的线程,主要分为用户级线程
和内核级线程
:
-
用户级线程
:即在用户空间下实现的线程,-
操作系统只能看到线程所属的进程但是不能看到线程
-
在这种模型下,我们需要自己定义线程的数据结构、创建、销毁、调度和维护等,这些线程运行在操作系统的某个进程内,然后操作系统直接对进程进行调度。
-
优点:即使操作系统原生不支持线程,也可以通过库函数来支持线程;线程的调度只发生在用户态,避免了操作系统从内核态到用户态的转换开销
-
缺点:由于操作系统看不见线程,不知道线程的存在,而 CPU 的时间片切换是以进程为维度的,所以如果进程中某个线程进行了耗时比较长的操作,那么由于用户空间中没有时钟中断机制,就会导致此进程中的其它线程因为得不到 CPU 资源而长时间的持续等待;另外,如果某个线程进行系统调用时比如缺页中断而导致了线程阻塞,此时操作系统也会阻塞住整个进程,即使这个进程中其它线程还在工作
-
-
内核级线程
:就是运行在内核空间的线程,- 直接由内核负责,只能由内核来完成线程的调度
- 每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情
- 可以直接使用操作系统中已经内置好的线程,线程的创建、销毁、调度和维护等,都是直接由操作系统的内核来实现,我们只需要使用系统调用就好了,不需要像用户级线程那样自己设计线程调度等
-
操作系统又根据用户线程和内核线程之间的对应关系,可以分为:
-
多对一线程模型:在多对一模型中,
- 多个用户线程映射到一个内核线程上
- 线程管理由用户空间中的线程库处理,非常高效
- 但是如果一个线程进行了阻塞的系统调用,那么即使其他用户线程还能够继续,也会阻塞整个进程
- 由于单个内核线程只能够在单个CPU上运行,因此多对一模型不允许在多个CPU之间拆分进程(一个进程的线程只能映射一个内核线程)
- 从并发性角度来总结下,虽然多对一模型允许开发人员创建任意多的用户线程,但是由于内核只能一次调度一个线程,所以并未增加并发性。现在已经几乎没有操作系统来使用这个模型了,因为它无法利用多个处理核。
-
一对一线程模型:
-
一对一模型克服了多对一模型的问题
-
一对一模型创建一个单独的内核线程来处理每个用户线程
-
但是,管理一对一模型的开销更大,涉及更多开销和减慢系统速度
-
此模型的大多数实现都限制了可以创建的线程数
-
从并发性角度来总结下,虽然一对一模型提供了更大的并发性,但是开发人员应注意不要在应用程序内创建太多线程(有时系统可能会限制创建线程的数量),因为管理一对一模型的开销更大。Windows (从 Win95 开始) 和 Linux 都实现了线程的一对一模型。
-
-
多对多线程模型
-
多对多模型将任意数量的用户线程复用到相同或更少数量的内核线程上,结合了一对一和多对一模型的最佳特性
-
用户对创建的线程数没有限制
-
阻止内核系统调用不会阻止整个进程
-
进程可以分布在多个处理器上
-
可以为各个进程分配可变数量的内核线程,具体取决于存在的 CPU 数量和其他因素
-
-
3 Java线程
-
线程库
:线程库就是为开发人员提供创建和管理线程的一套 API,通过线程库来创建、管理线程; -
线程库不仅可以在用户空间中实现,还可以在内核空间中实现。前者涉及仅在用户空间内实现的 API 函数,没有内核支持。后者涉及系统调用,也就是说调用库中的一个 API 函数将会导致对内核的系统调用,并且需要具有线程库支持的内核。
-
主要的三种线程库:
POSIX Pthreads
:可以作为用户或内核库提供,作为 POSIX 标准的扩展Win32 线程
:用于 Window 操作系统的内核级线程库Java 线程
:Java 线程 API 通常采用宿主系统的线程库来实现,也就是说在 Win 系统上,Java 线程 API 通常采用 Win API 来实现,在 UNIX 类系统上,采用 Pthread 来实现。
-
在 JDK 1.2 之前,Java 线程是基于称为 "绿色线程"(Green Threads)的用户级线程实现的,也就是说程序员大佬们为 JVM 开发了自己的一套线程库或者说线程管理机制。
-
在 JDK 1.2 及以后,JVM 选择了更加稳定且方便使用的操作系统原生的内核级线程,通过系统调用,将线程的调度交给了操作系统内核。而对于不同的操作系统来说,它们本身的设计思路基本上是完全不一样的,因此它们各自对于线程的设计也存在种种差异,所以 JVM 中明确声明了:虚拟机中的线程状态,不反应任何操作系统中的线程状态。
-
也就是说,在 JDK 1.2 及之后的版本中,Java 的线程很大程度上依赖于操作系统采用什么样的线程模型,这点在不同的平台上没有办法达成一致,JVM 规范中也并未限定 Java 线程需要使用哪种线程模型来实现,可能是一对一,也可能是多对多或多对一。
总结来说,回答下文题,现今 Java 中线程的本质,其实就是操作系统中的线程,其线程库和线程模型很大程度上依赖于操作系统(宿主系统)的具体实现,比如在 Windows 中 Java 就是基于 Wind32 线程库来管理线程,且 Windows 采用的是一对一的线程模型。