这个问题是安琪拉之前面试被问到的一个问题,正好顺着上一篇文章介绍完线程调用时的用户态和内核态的切换,后续把Java 并发的都一起讲了。
面试官:听前一个面试官说你Java并发这块掌握的不错,我们深入的交流一下;
我: 看了看面试官头部稀疏的结缔组织,已然觉得这场面试不简单,不过好在事前把安琪拉的博客看了个遍,有所准备,我回答说:咳咳,掌握的还算可以。
面试官:Java线程用过的吧?
我:用过。
面试官:那你给我讲讲Java线程和操作系统的?
我:啊!!!
剧情不应该这样的啊,开场不应该先是 synchronized 或者 volatile,再然后是线程池和AQS,怎么上来就整这玩意。
我:好的,那我分三段讲,
-
用户态的线程
-
内核态的线程
-
Java 线程源码
1. 用户态的线程
第一阶段:
其实早期的时候,操作系统是没有线程的概念,线程是后面加进来的,操作系统刚开始只有进程,操作系统分配资源的最小单位是进程,进程与进程之间相关隔离,每个进程有自己的内存空间,文件描述符,CPU调度以进程作为最小调度单元;
第二阶段:
初期的多线程,线程是在用户空间下实现的。
什么意思?我们都知道内存分用户空间和系统空间,系统空间是给操作系统使用的,用户空间是应用程序使用的,应用程序如果需要访问系统空间,需要进行系统调用,从用户态切换到内核态,这里详细可以参考我上一篇文章: [讲讲用户空间和内核空间]
那怎么在用户空间实现的多线程呢?
实际上是操作系统按进程维度来调度,操作系统是不去管你用户线程的切换的,应用程序自己在用户空间实现线程的创建、维护和调度。模型如下图:
当线程在用户空间下实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,而不能看到线程。所有的线程都是在用户空间实现。在操作系统看来,每一个进程只有一个线程。
这种方式的好处之一就是即使操作系统不支持线程,也可以通过库函数来支持线程。在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.
我们知道,每个线程都有它自己的线程上下文,线程上下文包括线程的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 文件,:
OpenJDK1.8源代码第44行,方法映射;追着 JVM_StartThread 进到 jvm.cpp
linux 系统下的,看 src/hotspot/os/linux/os_linux.cpp
主要关注 pthread_create 这里,是通过linux 的 c库函数完成系统调用,从用户态切到内核态完成线程的创建。
文中源代码地址:
Thread.c
pthread_create
os_linux
下一篇文章介绍 Java 线程的几种状态的切换, 也是非常重要的知识点。
最后说下怕误导大家,说下头发不属于结缔组织。
网友喜欢把剪头发说成:明天我就要去动手术了,头部结缔组织群体切割手术,祝我好运 !
参考:
[Understanding java’s native threads and the jvm]
原创不易,如果觉得自己看完有收获,帮忙转发一下或点个在看,文章更新比较慢,如果怕漏看可以星标一下,预祝小伙伴们新年快乐!
原文始发于微信公众号(安琪拉的博客):阿里面试官问我Java线程和操作系统线程什么关系
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/25269.html