从JDK源码角度分析Java线程

前言

在2018年的夏天,我开始了Java学习之路。当时我才学完C++,面向对象,数据结构。还没学到操作系统和网络,所以当我学到Java的并发之后,发现了线程这个概念。并且学会了怎么使用线程,我当时以为这是Java独有的,直到学完操作系统才知道,原来线程是操作系统级别的概念,那么一个疑惑就出现了,Java线程和操作系统线程,有什么关系呢?
PS:创作不易,求个关注!!

基础知识

为了能够更加让读者更加平缓的理解,所以我们首先来点基础知识。首先是线程。

线程:线程是一种轻量级的进程,这是大家的共识。线程是进程中的一个执行流,同一进程里面的多个线程共享进程的资源。这个解释我想基本了解线程的人都很清楚,这也是面试常用的回答。那你有没有想过,线程在操作系统里面到底是什么样子呢?关于这个问题的回答,我有过一个分享:从源码角度理解线程
在这个文章里,我从Linux源码的角度说明了线程在操作系统中的表现形式以及调度。这里不再展开。

Java线程。Java语言里面的线程,定义在Jdk里面:java.lang.Thread类表示的一个结构。
图片说明

Java创建进程的方式: 这个我们说几个常见的,不用苛求穷尽所有方式(要是那样有点孔乙己的茴香豆的茴字有几种写法)。

  • 继承Thread类,复写run方法。
  • 实现Runnable接口
  • 实现Callable接口
  • 线程池
    这是四种最常用的,当然还有反射等,哪个不太常见。这四种其实大同小异,1和2基本就是一个意思,在上面的图也看到了,继承Thread类其实和实现Runnable接口区别不大,Thread本身也是在实现Runnable接口。

JNI方法Java Native Interface。Java本地接口。众所周知Java是跨平台的,Java实现跨平台的核心是:JVM。Java会被编译成字节码,然后虚拟机执行字节码。Java之所以跨平台,是因为有了JVM这个翻译官。JVM在不同的平台是不一样的,但是对于同一份字节码,不同的JVM会翻译成当前环境能够执行的命令,比如在Linux和Windows上。那么如果有些方法,确实是需要调用操作系统提供的函数呢?不如分配直接内存(Direct Memory),这个是依赖了malloc函数。JNI提供了和本地方法交互的方式。所以JNI可以简单的理解为,使用在Java里面,调用C++/C实现的方法(因为os里面大部分都是c/c++实现的)。

JDK的结构:可能很多人都没看过JDK源码,以openJDK为例子:
图片说明

可以看到,jdk里面有大量的我们常用的工具包,以及JVM,这里的JVM是hotspot虚拟机,在第三个目录。这也就是大家常说的,JDK包括了JVM的原因

JDK中的JNI方法JNI方法主要分两大类,一种是我们自己实现的,另一种是JDK自带的。JDK中有很多方法都是JNI方法,对于这种方法,Java使用关键字native修饰。比如常见的hashcode方法
图片说明
这个方法的具体实现JVM里面,执行的时候动态链接的。在src/share/vm/synchonizer.cpp中:
图片说明
了解了这些,可以开始接下来的探索了。

new Thread 发生了什么

可以看到,new Thread之后,其实是进了init方法。
图片说明
看一下init方法做了啥。前边的参数校验去掉之后,可以看到init其实是在对Thread类的成员做一下赋值,至于都代表啥意思,暂时不关注。只需要关注光标所在的地方,这里的target,其实就是传进来的具体的执行逻辑,也就是一个Runnable的实现。
图片说明
这里就完成了创建,似乎没什么东西。确实,因为创建之后,还要做启动。调用satrt函数

Start

图片说明
可以看到,start的逻辑,似乎很简单,也没啥东西。首先这个方法是synchornized的,这个很好理解,因为这个对象可能多个其他线程持有,所以这里需要同步,防止多次启动。然后就是try里面的逻辑,satrt0(),这是个native方法,说明实现不在这里,在JDK里面。src/java.base/share/native/libjava/Thread.c
图片说明
这里只是定义了方法列表,真正的实现在:hotspot/share/prims/jvm.cpp里面。
图片说明
前边是参数校验,核心逻辑在第2897行,这里thread_entry就是我们传进来的runnable接口,sz是栈的大小,这里new了一个JavaThread,那么这个JavaThread到底是啥?

Java Thread

javaThread类的定义在:hotspot/runtime/thread.hpp里面:
图片说明
可以看到,这里是继承了Thread类。了解C++的人应该明白,hpp或者.h都是类的定义,真正的实现在.cpp中,所以JavaThread的构造函数在thread.cpp中:
图片说明
这里新建线程是通过调用os::create_thread实现的,好了,现在只需要搞明白os::create_thread就知道咋回事了。

os::create_thread

在不同的操作系统中创建线程的方式肯定是不一样的,虽然现在大部分都遵循posix标准。但是还是有一些区别,JVM把这些操作系统之间有区别的系统调用,放在了一个地方:os包下面
图片说明
我们这里只看Linux,create_thread在linux里面的实现。在os_linux.cpp中
图片说明
765行基本可以看到,其实create_thread,就是系统调用pthread_create.到这里,其实就明白了Java线程和操作系统线程的关系了

总结

看完上面总结一下。

  • Java线程其实和操作系统线程一一对应,创建一个Java线程就是创建了一个操作系统线程。
  • Java通过jdk中的JNI方法,来实现调用操作系统的pthread_create函数
  • Java自己不做线程的调度,其实调度都是通过操作系统完成。这和golang的goroutine有本质的区别。
    但是其实还是有很多疑问在里面。
    • 我们的参数怎么传递到操作系统呢
    • Java怎么获取线程的状态
    • Callable类型的线程也是这样吗它是怎么得到返回值的?
      这个我们接下来再说!
    • 创作不易,求个关注!!*
#阿里巴巴暑假实习面试##Java##学习路径#
全部评论
最近正在和一些同学准备春招秋招,有一起学习的同学私聊加群哦
点赞
送花
回复 分享
发布于 2022-03-18 21:51

相关推荐

拉哥聊校招:1.大厂看中的是计算机基础,项目的深度和思考,以及你对技术栈应用在你的项目的业务的思考,以及高并发(以Java为例嘛,就是JUC的掌握),数据库缓存这些。上述掌握了 也需要很长时间的,而且大部分人掌握的还是八股,但校招来说也是够了~(当然小厂一般看中你的上手能力,也就是所谓的“技术”嘛,能写接口也可以了),至于项目这块,因为大多数人都是烂大街项目嘛,所以你需要对于你写的项目需要体现你的思考才是,这些才是你的亮点所在。(前提是进入面试) 2.因为面试官几乎就是看三个模块,一个是实习经历(包括科研经历,假如有的话),一个是项目经历,一个是技能;三个模块的排序就看你对哪个掌握比较深,哪个更深,更有自信就将该模块放在前面。 3.专业技能你写的熟悉,是否真的熟悉,所谓的熟悉是你应用场景、原理都要很懂才叫熟悉,不然的话你经不住面试官拷打很减分的;或许可以考虑换一个说法。技能这块最好是罗列一下,清晰地按照模块分层写:语言及基础、框架、中间件、计算机基础等;(不过你分层写的不错) 4.项目这块最好按照STAR法则去写,按照按照四个模块,项目描述,项目使用的技术栈,项目难点亮点(可以适当加粗),项目做完的收获这样子。我们都知道大部分同学的项目都是烂大街的,这其实没所谓,哪有那么多同学做高并发的项目呢哈哈,很多大厂里面的员工也只是负责 toB 业务的他们也不知道高并发呀~所以,重点在于你对你写的项目的深度思考,你在面对什么相对复杂的业务时用了啥技术去解决?这个技术是否经过验证?权衡?以及带来的后果是啥,浓缩成一句话,你要把你的项目当成要还原一个现成的app去写最好。你是否准备对项目的难点亮点的问题呢?项目问题你这边虽然都是技术栈堆砌,但是问题不大了,整天看起来还是可以的(这边可以给你简历的项目提一些面试官或许会问的问题或者拓展问题) 5.学历很优秀,完全有可能去大厂的呀,现在是秋招提前批和日常实习的专场了,可以好好准备一下,然后充提前批吧~做一个简介:假如需要模拟面试,可以来滴滴我哈哈,一般两次到三次模拟面试就可以避免踩坑了(再强的面霸第一次面试的时候都是做炮灰的,很多学历很好的同学的第一面往往是大厂面试,做炮灰的几率更大,因为小厂也不傻,不给机会面试,所以我们可以给你一次模拟面试,让你真正掌握面试的重点的技巧,而不只是单单背八股文而已~以及包括项目的亮点和难点辅导),简历辅导也是如此。 6.最后的最后,加油努力,祝你成功、顺利。
点赞 评论 收藏
分享
点赞 2 评论
分享
牛客网
牛客企业服务