本文转载自微信公众号“Angela的博客”,作者Angela的博客。转载本文请联系Angela博客公众号。这个问题是安吉拉在之前的采访中被问到的问题。只是跟着上篇文章介绍了线程调用时用户态和内核态的切换,后面会一起讲Java并发。面试官:听之前的面试官说你对Java并发掌握的很好,我们来深入交流一下;I:看了面试官头上稀疏的结缔组织,我已经觉得这个面试不容易了,不过提前知道就好了。把Angela的博客全看完了,心里有了准备,回道:咳咳,我能把握的很好。面试官:你用过Java线程吗?我可以。面试官:那跟我说说Java线程和操作系统?我:啊!!!剧情不应该是这样的。开场场景应该不是synchronized或者volatile,然后是线程池和AQS。东西。我:好吧,那我分三段讲,用户态线程,内核态线程,Java线程源码1.用户态线程,第一阶段:其实在早期,操作系统没有线程的概念,后来加入了线程。一开始,操作系统只有进程,操作系统分配资源的最小单位是进程。进程彼此隔离。每个进程都有自己的内存空间、文件描述符,CPU调度以进程为最小调度单位;第二阶段:初始多线程,线程在用户空间实现。这是什么意思?我们都知道内存分为用户空间和系统空间。系统空间供操作系统使用,用户空间供应用程序使用。如果应用程序需要访问系统空间,则需要进行系统调用,从用户态切换到内核态。具体可以参考我之前的文章:【浅谈用户空间和内核空间】如何在用户空间实现多线程?其实操作系统是按照进程维度来调度的,操作系统不管你。对于用户线程的切换,应用程序自己实现了用户空间线程的创建、维护和调度。模型如下图所示:当线程在用户空间实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,看不到线程。所有线程都在用户空间中实现。从操作系统的角度来看,每个进程只有一个线程。这种方式的好处之一是即使操作系统不支持线程,也可以通过库函数支持线程。在JDK1.1中,使用绿色线程代替了原来的线程。下面是关于greenthread的讲解,因为greenthread不是今天的重点,就不赘述了。绿色线程是由运行时环境或虚拟机(VM)而非本机底层操作系统调度的线程。绿色线程不依赖底层系统功能,模拟多线程运行。此类线程的管理和部署发生在用户空间而不是内核空间,因此它们可以在没有本机线程支持的环境中工作。在Java1.1中,绿色线程(至少在Solaris上)是JVM中唯一使用的线程模型。由于绿色线程与原生线程相比在使用上有一定的局限性,所以在后续的Java版本中都放弃了绿色线程,转而使用原生线程。这种模式的优缺点非常明显:缺点:由于操作系统不知道线程的存在,CPU的时间片切换是基于进程的。如果一个线程在进程中执行一些耗时的操作,就会阻塞整个进程。另外,当进程中的某个线程(绿色线程)进行系统调用时,如网络IO、缺页中断等导致线程阻塞的操作,操作系统也会阻塞整个进程,即使其他进程中的线程仍在工作。优点:使用库函数实现线程切换,无需从用户态切换到内核态。这味道不熟悉。顺便说一下,Go的协程借鉴了一部分这个思想。2、内核态线程是Java1.2以后的。Linux中的JVM是基于pthreads实现的。可以直接说Java线程是由操作系统实现的,是1:1的关系。今天Java中线程的本质其实就是操作系统中的线程。另外看了很多资料说Java线程是使用LWP(LightweightProcess)实现的。事实上,从Linux内核2.6开始,LinuxThread被新的线程实现方式NPTL所取代,NPTL解决了LinuxThread中大部分不兼容POSIX标准的特性,提供了更好的性能、可扩展性和可维护性等。LinuxThread使用一个1*1的模型,即每个用户态线程都有一个内核管理实体与之对应。这个内核对应的管理实体是进程,也称为LWP(LightweightProcess)。希望进一步了解NPTL可以去看看NPTL的详细介绍。我们知道,每个线程都有自己的线程上下文,其中包括线程ID、堆栈、程序计数器和通用寄存器的集合。总觉得文脉这个词含糊不清,但又找不到更合适的词来形容。线程有自己独立的上下文,由操作系统调度,但也有一个缺点,就是线程消耗资源太多。比如在Linux上,一个线程默认的栈大小是1M,单机上创建上万个线程就有点困难了。所以后来在编程语言层面,出现了协程。协程的模式有点类似于上面两种方式的结合,即在用户态进行线程资源切换,操作系统在内核层进行线程调度。协程与操作系统的线程之间存在映射关系。比如我们建了m个协程,需要在N个线程上执行。这就是m:n方案,n个线程也是由操作系统调度实现的。另外,协程是按需使用栈内存,所以理论上可以轻松创建数百万个协程。目前对协程支持最好的是go语言,但是现在OpenJDK社区也在为JDK加入对协程的支持。3、线程的源码当我们在Java中调用newThread(Runnable***).start()方法时,如何从用户态切换到内核态,发送系统调用,创建线程在操作系统内核层?这可以逐步深入。关键点是在JVM层系统调用pthread_create创建线程。第一个是本地方法:privatenativevoidstart0();进入Thread.c文件:OpenJDK1.8源码第44行,方法映射;按照JVM_StartThread进入jvm.cpplinux系统,查看src/hotspot/os/linux/os_linux.cpp主要关注pthread_create。这里通过linux的c库函数完成系统调用,从用户态切换到内核态完成线程创建。文中源码地址:Thread.cpthread_createos_linux
