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

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

[[376045]]

本文转载自微信公众号「安琪拉的博客」,作者安琪拉的博客 。转载本文请联系安琪拉的博客公众号。

 这个问题是安琪拉之前面试被问到的一个问题,正好顺着上一篇文章介绍完线程调用时的用户态和内核态的切换,后续把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库函数完成系统调用,从用户态切到内核态完成线程的创建。

文中源代码地址:

  1. Thread.c 
  2.  
  3. pthread_create 
  4.  
  5. os_linux 

 

责任编辑:武晓燕 来源: 安琪拉的博客
相关推荐

2023-12-20 14:35:37

Java虚拟线程

2021-05-08 07:53:33

面试线程池系统

2023-11-06 17:39:35

JavaArrayList线程

2024-09-11 22:51:19

线程通讯Object

2020-10-26 07:07:50

线程安全框架

2021-04-19 09:27:03

Java线程操作系统

2021-12-02 08:19:06

MVCC面试数据库

2024-04-10 09:47:59

Java调度虚拟线程

2021-06-03 08:55:54

分布式事务ACID

2024-04-02 09:45:27

线程池Executors开发

2022-11-25 17:29:27

分布式事务

2022-06-02 09:29:55

线程组线程树状结构

2024-09-09 15:09:30

2024-03-11 18:18:58

项目Spring线程池

2021-09-27 07:11:18

MySQLACID特性

2021-04-25 09:36:20

Go协程线程

2020-05-15 11:14:58

操作系统面试官运行

2022-05-24 08:03:28

InnoDBMySQL数据

2021-08-12 14:49:44

操作系统线程进程

2010-08-23 15:06:52

发问
点赞
收藏

51CTO技术栈公众号