阿里面试官问我Java线程和操作系统线程什么关系

共 2849字,需浏览 6分钟

 ·

2021-03-22 16:32

这个问题是安琪拉之前面试被问到的一个问题,正好顺着上一篇文章介绍完线程调用时的用户态和内核态的切换,后续把Java 并发的都一起讲了。

面试官:听前一个面试官说你Java并发这块掌握的不错,我们深入的交流一下;

我:  看了看面试官头部稀疏的结缔组织,已然觉得这场面试不简单,不过好在事前把安琪拉的博客看了个遍,有所准备,我回答说:咳咳,掌握的还算可以。

面试官:Java线程用过的吧?

我:用过。

面试官:那你给我讲讲Java线程和操作系统的?

我:啊!!!


剧情不应该这样的啊,开场不应该先是 synchronized 或者 volatile,再然后是线程池和AQS,怎么上来就整这玩意。

我:好的,那我分三段讲,

  • 用户态的线程

  • 内核态的线程

  • Java 线程源码

1. 用户态的线程

第一阶段:

其实早期的时候,操作系统是没有线程的概念,线程是后面加进来的,操作系统刚开始只有进程,操作系统分配资源的最小单位是进程,进程与进程之间相关隔离,每个进程有自己的内存空间,文件描述符,CPU调度以进程作为最小调度单元;

第二阶段:

初期的多线程,线程是在用户空间下实现的。

什么意思?我们都知道内存分用户空间和系统空间,系统空间是给操作系统使用的,用户空间是应用程序使用的,应用程序如果需要访问系统空间,需要进行系统调用,从用户态切换到内核态,这里详细可以参考我上一篇文章: [讲讲用户空间和内核空间]

那怎么在用户空间实现的多线程呢?

实际上是操作系统按进程维度来调度,操作系统是不去管你用户线程的切换的,应用程序自己在用户空间实现线程的创建、维护和调度。模型如下图:

1be3a3618000bb85d94edce0679e796d.webp

当线程在用户空间下实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,而不能看到线程。所有的线程都是在用户空间实现。在操作系统看来,每一个进程只有一个线程。

这种方式的好处之一就是即使操作系统不支持线程,也可以通过库函数来支持线程。在JDK1.1中,就用的绿色线程,而不是原始线程。

下面是关于green thread的解释,因为green thread不是今天的重点,就不细说了。

green threads 是一种由运行环境或虚拟机(VM)调度,而不是由本地底层操作系统调度的线程。绿色线程并不依赖底层的系统功能,模拟实现了多线程的运行,这种线程的管理调配发生在用户空间而不是内核空间,所以它们可以在没有原生线程支持的环境中工作。
在Java 1.1中,绿色线程(至少在 Solaris 上)是JVM 中使用的唯一一种线程模型。由于绿色线程和原生线程比起来在使用时有一些限制,随后的 Java 版本中放弃了绿色线程,转而使用native threads

这种模式的优点和缺点都非常明显:

缺点: 因为操作系统不知道线程的存在,CPU的时间片切换是以进程为维度的,如果进程中有某个线程进行了某些耗时长的操作,会阻塞整个进程。另外当一个进程中的某一个线程(绿色线程)进行系统调用时,比如网络IO、缺页中断等操作而导致线程阻塞,操作系统也会阻塞整个进程,即使这个进程中其它线程还在工作。

优点: 使用库函数来实现的线程切换,就免去了用户态到内核态的切换,这个味道熟不熟,对了,Go的协程就有借鉴了一部分这个思想。

2. 内核态的线程

在 Java1.2 之后. Linux中的JVM是基于pthread实现的, 可以直接说 Java 线程就是依赖操作系统实现的,是1:1的关系。

现在的Java中线程的本质,其实就是操作系统中的线程

另外我看很多资料上说 Java线程的实现采用的是LWP(轻量级进程),实际上从Linux 内核2.6开始,就把LinuxThread 换成了新的线程实现方式NPTL,NPTL解决了LinuxThread中绝大多数跟POSIX标准不兼容的特性,并提供了更好的性能,可扩展性及可维护性等等。

LinuxThread使用的是1 * 1模型,即每一个用户态线程都有一个内核的管理实体跟其对应,这个内核对应的管理实体就是进程,又称LWP(轻量级进程)

希望了解更多NPTL的可以去看详细介绍NPTL.

ee7c437dd1255156ac8c3a60e05eeb21.webp

我们知道,每个线程都有它自己的线程上下文,线程上下文包括线程的ID、栈、程序计数器、通用的寄存器等的合集。总觉得上下文这个词很模棱二可,但是发现也找不到更合适的词来描述。

线程有自己的独立的上下文,由操作系统调度,但是也有一个缺点,那就是线程消耗资源太大了,例如在linux上,一个线程默认的栈大小是1M,单机创建几万个线程就有点吃力了。所以后来在编程语言的层面上,就出现了协程这个东西。

协程的模式有点类似结合了上面二种方式,即是在用户态做线程资源切换,也让操作系统在内核层做线程调度。

协程跟操作系统的线程是有映射关系的,例如我们建了m个协程,需要在N个线程上执行,这就是m: n的方案,这n个线程也是靠操作系统调度实现。

另外协程是按需使用栈内存的,所以理论上可以轻轻松松创建百万级的协程。

目前协程这块支持的最好的是go语言, 不过现在OpenJDK社区也正在为JDK增加协程的支持。

3. 线程的源码

我们在Java中调用 new Thread(Runnable ***).start()  方法时,怎么从用户态切到内核态,发送系统调用,在操作系统内核层中创建一个线程的呢?

这个可以一步步往下钻,关键点最后在JVM层系统调用pthread_create创建线程。

首先是native方法:  private native void start0();

下到Thread.c 文件,:

53ace5aac28625fe2541bbae046b67d6.webp

OpenJDK1.8源代码第44行,方法映射;追着 JVM_StartThread 进到 jvm.cpp

b351b4f9486707cb80ec25632f4f4a5f.webp

linux 系统下的,看 src/hotspot/os/linux/os_linux.cpp

4d785280ef20756dd47461abebcda00e.webp

主要关注 pthread_create 这里,是通过linux 的 c库函数完成系统调用,从用户态切到内核态完成线程的创建。

文中源代码地址:

Thread.c

pthread_create

os_linux

下一篇文章介绍 Java 线程的几种状态的切换, 也是非常重要的知识点。

最后说下怕误导大家,说下头发不属于结缔组织。

网友喜欢把剪头发说成:明天我就要去动手术了,头部结缔组织群体切割手术,祝我好运 !

参考:

[Understanding java's native threads and the jvm]

原创不易,如果觉得自己看完有收获,帮忙转发一下或点个在看文章更新比较慢,如果怕漏看可以星标一下,预祝小伙伴们新年快乐!


推荐阅读



NDK系列-如何使用C/C++编写带EGL功能的NativeActivity


Android自定义View-引导蒙版


Android自定义View基础


Kotlin协程与JetPack组件初探


Retrofit流程解析


通过终端命令生成并在手机上运行dex文件


Android

技术堆栈

扫描二维码




浏览 51
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报