0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于JDK 1.8来分析Thread类的源码

OSC开源社区 来源:冰河技术 2023-02-06 17:12 次阅读

前言

最近和一个工作了7年的朋友聊天,他跟我说起了他去XXX公司面试的情况,面试官的一个问题把他打懵了!竟然问他:你经常使用Thread类创建线程,那你看过Thread类的源码吗?Thread类创建线程的流程是什么?如何中断一个正在执行的线程?我这个朋友平时觉得Thread类非常简单,自然是没看过Thread类的源码,然后,就没有然后了!!!

所以,我们学习技术不仅需要知其然,更需要知其所以然,今天,我们就一起来简单看看Thread类的源码。

注意:本文是基于JDK 1.8来进行分析的。

Thread类的继承关系

我们可以使用下图来表示Thread类的继承关系。

3ec8f1fe-a48d-11ed-bfe3-dac502259ad0.jpg

由上图我们可以看出,Thread类实现了Runnable接口,而Runnable在JDK 1.8中被@FunctionalInterface注解标记为函数式接口,Runnable接口在JDK 1.8中的源代码如下所示。

@FunctionalInterface
publicinterfaceRunnable{
publicabstractvoidrun();
}

Runnable接口的源码比较简单,只是提供了一个run()方法,这里就不再赘述了。

接下来,我们再来看看@FunctionalInterface注解的源码,如下所示。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public@interfaceFunctionalInterface{}

可以看到,@FunctionalInterface注解声明标记在Java类上,并在程序运行时生效。

Thread类的源码剖析

Thread类定义

Thread在java.lang包下,Thread类的定义如下所示。

publicclassThreadimplementsRunnable{

加载本地资源

打开Thread类后,首先,我们会看到在Thread类的最开始部分,定义了一个静态本地方法registerNatives(),这个方法主要用来注册一些本地系统的资源。并在静态代码块中调用这个本地方法,如下所示。

//定义registerNatives()本地方法注册系统资源
privatestaticnativevoidregisterNatives();
static{
//在静态代码块中调用注册本地系统资源的方法
registerNatives();
}

Thread中的成员变量

Thread类中的成员变量如下所示。

//当前线程的名称
privatevolatileStringname;
//线程的优先级
privateintpriority;
privateThreadthreadQ;
privatelongeetop;
//当前线程是否是单步线程
privatebooleansingle_step;
//当前线程是否在后台运行
privatebooleandaemon=false;
//Java虚拟机的状态
privatebooleanstillborn=false;
//真正在线程中执行的任务
privateRunnabletarget;
//当前线程所在的线程组
privateThreadGroupgroup;
//当前线程的类加载器
privateClassLoadercontextClassLoader;
//访问控制上下文
privateAccessControlContextinheritedAccessControlContext;
//为匿名线程生成名称的编号
privatestaticintthreadInitNumber;
//与此线程相关的ThreadLocal,这个Map维护的是ThreadLocal类
ThreadLocal.ThreadLocalMapthreadLocals=null;
//与此线程相关的ThreadLocal
ThreadLocal.ThreadLocalMapinheritableThreadLocals=null;
//当前线程请求的堆栈大小,如果未指定堆栈大小,则会交给JVM来处理
privatelongstackSize;
//线程终止后存在的JVM私有状态
privatelongnativeParkEventPointer;
//线程的id
privatelongtid;
//用于生成线程id
privatestaticlongthreadSeqNumber;
//当前线程的状态,初始化为0,代表当前线程还未启动
privatevolatileintthreadStatus=0;
//由(私有)java.util.concurrent.locks.LockSupport.setBlocker设置
//使用java.util.concurrent.locks.LockSupport.getBlocker访问
volatileObjectparkBlocker;
//Interruptible接口中定义了interrupt方法,用来中断指定的线程
privatevolatileInterruptibleblocker;
//当前线程的内部锁
privatefinalObjectblockerLock=newObject();
//线程拥有的最小优先级
publicfinalstaticintMIN_PRIORITY=1;
//线程拥有的默认优先级
publicfinalstaticintNORM_PRIORITY=5;
//线程拥有的最大优先级
publicfinalstaticintMAX_PRIORITY=10;

从Thread类的成员变量,我们可以看出,Thread类本质上不是一个任务,它是一个实实在在的线程对象,在Thread类中拥有一个Runnable类型的成员变量target,而这个target成员变量就是需要在Thread线程对象中执行的任务。

线程的状态定义

在Thread类的内部,定义了一个枚举State,如下所示。

publicenumState{
//初始化状态
NEW,
//可运行状态,此时的可运行包括运行中的状态和就绪状态
RUNNABLE,
//线程阻塞状态
BLOCKED,
//等待状态
WAITING,
//超时等待状态
TIMED_WAITING,
//线程终止状态
TERMINATED;
}

这个枚举类中的状态就代表了线程生命周期的各状态。我们可以使用下图来表示线程各个状态之间的转化关系。

3ed78e4e-a48d-11ed-bfe3-dac502259ad0.jpg

NEW:初始状态,线程被构建,但是还没有调用start()方法。

RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。

BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。

WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。

TIME_WAITING:超时等待状态。可以在一定的时间自行返回。

TERMINATED:终止状态,当前线程执行完毕。

Thread类的构造方法

Thread类中的所有构造方法如下所示。

publicThread(){
init(null,null,"Thread-"+nextThreadNum(),0);
}
publicThread(Runnabletarget){
init(null,target,"Thread-"+nextThreadNum(),0);
}
Thread(Runnabletarget,AccessControlContextacc){
init(null,target,"Thread-"+nextThreadNum(),0,acc,false);
}
publicThread(ThreadGroupgroup,Runnabletarget){
init(group,target,"Thread-"+nextThreadNum(),0);
}
publicThread(Stringname){
init(null,null,name,0);
}
publicThread(ThreadGroupgroup,Stringname){
init(group,null,name,0);
}
publicThread(Runnabletarget,Stringname){
init(null,target,name,0);
}
publicThread(ThreadGroupgroup,Runnabletarget,Stringname){
init(group,target,name,0);
}
publicThread(ThreadGroupgroup,Runnabletarget,Stringname,
longstackSize){
init(group,target,name,stackSize);
}

其中,我们最经常使用的就是如下几个构造方法了。

publicThread(){
init(null,null,"Thread-"+nextThreadNum(),0);
}
publicThread(Runnabletarget){
init(null,target,"Thread-"+nextThreadNum(),0);
}
publicThread(Stringname){
init(null,null,name,0);
}
publicThread(ThreadGroupgroup,Stringname){
init(group,null,name,0);
}
publicThread(Runnabletarget,Stringname){
init(null,target,name,0);
}
publicThread(ThreadGroupgroup,Runnabletarget,Stringname){
init(group,target,name,0);
}

通过Thread类的源码,我们可以看出,Thread类在进行初始化的时候,都是调用的init()方法,接下来,我们看看init()方法是个啥。

init()方法

privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,longstackSize){
init(g,target,name,stackSize,null,true);
}
privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,
longstackSize,AccessControlContextacc,
booleaninheritThreadLocals){
//线程的名称为空,抛出空指针异常
if(name==null){
thrownewNullPointerException("namecannotbenull");
}

this.name=name;
Threadparent=currentThread();
//获取系统安全管理器
SecurityManagersecurity=System.getSecurityManager();
//线程组为空
if(g==null){
//获取的系统安全管理器不为空
if(security!=null){
//从系统安全管理器中获取一个线程分组
g=security.getThreadGroup();
}
//线程分组为空,则从父线程获取
if(g==null){
g=parent.getThreadGroup();
}
}
//检查线程组的访问权限
g.checkAccess();
//检查权限
if(security!=null){
if(isCCLOverridden(getClass())){
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
//当前线程继承父线程的相关属性
this.group=g;
this.daemon=parent.isDaemon();
this.priority=parent.getPriority();
if(security==null||isCCLOverridden(parent.getClass()))
this.contextClassLoader=parent.getContextClassLoader();
else
this.contextClassLoader=parent.contextClassLoader;
this.inheritedAccessControlContext=
acc!=null?acc:AccessController.getContext();
this.target=target;
setPriority(priority);
if(inheritThreadLocals&&parent.inheritableThreadLocals!=null)
this.inheritableThreadLocals=
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/*StashthespecifiedstacksizeincasetheVMcares*/
this.stackSize=stackSize;

//设置线程id
tid=nextThreadID();
}

Thread类中的构造方法是被创建Thread线程的线程调用的,此时,调用Thread的构造方法创建线程的线程就是父线程,在init()方法中,新创建的Thread线程会继承父线程的部分属性。

run()方法

既然Thread类实现了Runnable接口,则Thread类就需要实现Runnable接口的run()方法,如下所示。

@Override
publicvoidrun(){
if(target!=null){
target.run();
}
}

可以看到,Thread类中的run()方法实现非常简单,只是调用了Runnable对象的run()方法。所以,真正的任务是运行在run()方法中的。

另外,需要注意的是:直接调用Runnable接口的run()方法不会创建新线程来执行任务,如果需要创建新线程执行任务,则需要调用Thread类的start()方法。

start()方法

publicsynchronizedvoidstart(){
//线程不是初始化状态,则直接抛出异常
if(threadStatus!=0)
thrownewIllegalThreadStateException();
//添加当前启动的线程到线程组
group.add(this);
//标记线程是否已经启动
booleanstarted=false;
try{
//调用本地方法启动线程
start0();
//将线程是否启动标记为true
started=true;
}finally{
try{
//线程未启动成功
if(!started){
//将线程在线程组里标记为启动失败
group.threadStartFailed(this);
}
}catch(Throwableignore){
/*donothing.Ifstart0threwaThrowablethen
itwillbepassedupthecallstack*/
}
}
}

privatenativevoidstart0();

从start()方法的源代码,我们可以看出:start()方法使用synchronized关键字修饰,说明start()方法是同步的,它会在启动线程前检查线程的状态,如果不是初始化状态,则直接抛出异常。所以,一个线程只能启动一次,多次启动是会抛出异常的。

这里,也是面试的一个坑:面试官:【问题一】能不能多次调用Thread类的start()方法来启动线程吗?【问题二】多次调用Thread线程的start()方法会发生什么?【问题三】为什么会抛出异常?

调用start()方法后,新创建的线程就会处于就绪状态(如果没有分配到CPU执行),当有空闲的CPU时,这个线程就会被分配CPU来执行,此时线程的状态为运行状态,JVM会调用线程的run()方法执行任务。

sleep()方法

sleep()方法可以使当前线程休眠,其代码如下所示。

//本地方法,真正让线程休眠的方法
publicstaticnativevoidsleep(longmillis)throwsInterruptedException;

publicstaticvoidsleep(longmillis,intnanos)
throwsInterruptedException{
if(millis< 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos >999999){
thrownewIllegalArgumentException(
"nanosecondtimeoutvalueoutofrange");
}

if(nanos>=500000||(nanos!=0&&millis==0)){
millis++;
}
//调用本地方法
sleep(millis);
}

sleep()方法会让当前线程休眠一定的时间,这个时间通常是毫秒值,这里需要注意的是:调用sleep()方法使线程休眠后,线程不会释放相应的锁。

join()方法

join()方法会一直等待线程超时或者终止,代码如下所示。

publicfinalsynchronizedvoidjoin(longmillis)
throwsInterruptedException{
longbase=System.currentTimeMillis();
longnow=0;

if(millis< 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

public final synchronized void join(long millis, int nanos)
    throws InterruptedException {

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos >999999){
thrownewIllegalArgumentException(
"nanosecondtimeoutvalueoutofrange");
}

if(nanos>=500000||(nanos!=0&&millis==0)){
millis++;
}

join(millis);
}

publicfinalvoidjoin()throwsInterruptedException{
join(0);
}

join()方法的使用场景往往是启动线程执行任务的线程,调用执行线程的join()方法,等待执行线程执行任务,直到超时或者执行线程终止。

interrupt()方法

interrupt()方法是中断当前线程的方法,它通过设置线程的中断标志位来中断当前线程。此时,如果为线程设置了中断标志位,可能会抛出InteruptedExeption异常,同时,会清除当前线程的中断状态。这种方式中断线程比较安全,它能使正在执行的任务执行能够继续执行完毕,而不像stop()方法那样强制线程关闭。代码如下所示。

publicvoidinterrupt(){
if(this!=Thread.currentThread())
checkAccess();

synchronized(blockerLock){
Interruptibleb=blocker;
if(b!=null){
interrupt0();//Justtosettheinterruptflag
b.interrupt(this);
return;
}
}
//调用本地方法中断线程
interrupt0();
}
privatenativevoidinterrupt0();

总结

作为技术人员,要知其然,更要知其所以然,我那个朋友技术本身不错,各种框架拿来就用,基本没看过常用的框架源码和JDK中常用的API,属于那种CRUD型程序员,这次面试就栽在了一个简单的Thread类上,所以,大家在学会使用的时候,一定要了解下底层的实现才好啊!






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10824

    浏览量

    211113
  • JVM
    JVM
    +关注

    关注

    0

    文章

    157

    浏览量

    12206
  • JDK
    JDK
    +关注

    关注

    0

    文章

    81

    浏览量

    16576
  • Thread
    +关注

    关注

    2

    文章

    83

    浏览量

    25904

原文标题:一文讲尽Thread类的源码精髓

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    RT-Thread记录(十一、UART设备—源码解析)

    一文带你深入理解 RT-Thread I/O 设备模型 — UART 设备源码分析
    的头像 发表于 07-01 11:24 5369次阅读
    RT-<b class='flag-5'>Thread</b>记录(十一、UART设备—<b class='flag-5'>源码</b>解析)

    JDK动态代理的原理

    在Java中,动态代理是一种机制,允许在运行时动态地创建代理对象代替某个实际对象,从而在其前后执行额外的逻辑。 为什么JDK动态代理只能代理接口实现,原因是JDK动态代理是基于接口
    的头像 发表于 09-30 10:51 553次阅读

    RT-thread源码移植到STM32F10x和STM32F4xx

    RT-thread源码移植到STM32F10x和STM32F4xx: 一、源码下载 点击入门->下载   在历史版本里边随便选取一个   会进入百度云盘的下载地址,里边有全部版本的源码
    的头像 发表于 11-15 09:38 2410次阅读
    RT-<b class='flag-5'>thread</b><b class='flag-5'>源码</b>移植到STM32F10x和STM32F4xx

    RT-Thread应用笔记(设备驱动)配套资料含源码

    本帖最后由 ichenchunlai 于 2020-4-7 18:55 编辑 资料为RT-Thread应用笔记(设备驱动),压缩包内包含PDF文档及源码。提供了《从裸机开始,创建一个
    发表于 04-07 18:52

    JDK 15安装步骤及新特性

    。  Nashorn 是 JDK 1.8 引入的一个 JavaScript 脚本引擎,用来取代 Rhino 脚本引擎。Nashorn 是 ECMAScript-262 5.1 的完整实现,增强了 Java
    发表于 12-23 17:36

    看看基于JDK中自带JVM工具的用法

    进行分析和解决,不过这些需要对基础工具熟练使用才行,而很多JDK自身的能力又是经常被忽略的;在jdk的bin目录中,有很多自带工具可以用于对JVM的分析;上述是基于
    发表于 11-16 15:30

    基于RT-Thread的FM1702源码

    RT-Thread是一款来自中国的开源嵌入式实时操作系统,包括一系列应用组件和驱动框架,如TCP/IP协议栈,虚拟文件系统,POSIX接口,图形用户界面。---(转自RTT官网)。 FM1702是无线射频的一种,现将其移植到RT-Thread操作系统中。
    发表于 12-28 10:54 15次下载

    虚拟机:CentOS 7通过yum安装JDK1.8的方法

    虚拟机:CentOS 7通过yum安装JDK1.8的方法
    的头像 发表于 07-02 18:02 3161次阅读

    Java开发工具包JDK1.8D安装说明书

    本文档的主要内容介绍的是Java开发工具包JDK1.8D安装说明书资料免费下载。
    发表于 07-16 08:00 33次下载
    Java开发工具包<b class='flag-5'>JDK1.8</b>D安装说明书

    源码解读·RT-Thread操作系统从开机到关机(转)

    源码解读·RT-Thread操作系统从开机到关机本篇内容比较简单,但却很繁琐,篇幅也很长,毕竟是囊括了整个操作系统的生命周期。这篇文章的目的是作为后续设计多任务开发的铺垫,后续会单独再抽出一篇分析
    发表于 12-16 16:58 11次下载
    <b class='flag-5'>源码</b>解读·RT-<b class='flag-5'>Thread</b>操作系统从开机到关机(转)

    HC32F460移植RT-Thread Nano+FinSh工程源码下载

    HC32F460移植RT-Thread Nano+FinSh工程源码下载
    发表于 01-05 10:30 6次下载

    JDK1.8使用的接口

    实现了俩接口,本身是个class。这个是Future的实现,使用completionStage接口去支持完成时触发的函数和操作。
    的头像 发表于 07-26 11:09 887次阅读

    JDK中java.util.HashSet 的介绍

    JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,相对于早期版本的 JDK HashMap 实现,新增了红黑树作为底层数据结构,在数据量较大且哈希碰撞较多时,能够极大的增加检索
    的头像 发表于 10-09 10:50 540次阅读
    <b class='flag-5'>JDK</b>中java.util.HashSet <b class='flag-5'>类</b>的介绍

    JDK中常见的Lamada表达式

    JDK中有许多函数式接口,也会有许多方法会使用函数式接口作为参数,同时在各种源码中也大量使用了这些方法,那么我们在实际工作中应该如何使用!我们就来盘一盘,这样也有助于写出优雅的代码,使我们在阅读源码
    的头像 发表于 10-10 15:07 494次阅读
    <b class='flag-5'>JDK</b>中常见的Lamada表达式

    JDK中java.lang.Arrays 源码解析

    日常开发中,我们会使用各种工具,利用封装好的轮子,能让我们的开发事半功倍。但是在JDK中,有一个特别的工具——java.lang.Arrays.class,其源码实现还是挺精湛,接
    的头像 发表于 10-11 15:31 568次阅读
    <b class='flag-5'>JDK</b>中java.lang.Arrays <b class='flag-5'>类</b>的<b class='flag-5'>源码</b>解析