为什么start方法会调用run方法?(为什么不能直接run来实现创建线程?)

我们知道再创建线程的时候是使用Thread类中的start方法,那么为什么不直接用Thread类中的run方法呢?
来直接上源码:

  1. Thread中的run方法如下。
    在这里插入图片描述
    那么target是什么呢?
    在这里插入图片描述
    我们可以看到是一个Runable对象。那么Thread类中的run方法就是说:如果有Runnable子类对象(当然Runable是接口,没有直接对象),就会调用run方法,如果没有该对象,则什么操作都没有!
    这是官方文档对该方法的解释:大致意思如上,还多了一条就是Thread类的子类应该覆写此方法。那么为什么要覆写run方法?
    我们创建线程有三种方法(具体详细看线程这篇文章),而run方法中规定了线程对象要执行的代码。(也是就是线程要执行的任务操作,通俗的讲就是线程对象要干的事)

在这里插入图片描述
通过上边,我们知道如果直接调用Thread类中的run方法,不覆写该方法的话,什么结果都没有,因为Thread类中的run方法虽然实现了Runable接口,但是本身还是一个空方法,这和Runable接口中没有方法体的run方法没有什么区别。(那么为什么Thread要implems实现Runable?

2.那么既然该方法是空的,那么我们覆写下该方法不就可以通过Threade.run()来实现创建线程了吗?
我们来通过下面代码来测试下:
首先自定义一个线程类来继承Thread,因为只有继承了父类,才能覆写父类方法。

//自定义线程类
class MyThread extends Thread{
    //在类中指定线程对象要执行的功能代码
    //重写run方法
    @Override
    public void run() {
        while(true){
            System.out.println(this.getName());
        }
    }
}

MyThread线程也要执行的任务(也就是要干的事)是打印执行本方法的线程名字。
主函数中创建我们的线程。并将我们的线程名命名为:“我的第一个线程”,然后调用我们覆写之后的run方法,并在主函数中打印主线程的名字。来看结果:我们预料的结果应该是main线程和“我的第一个线程”抢占CUP内存,交替打印到控制台。

    public static void main(String[] args) {
        MyThread thread=new MyThread();
        thread.setName("我的第一个线程");
        thread.run();
        while(true){
            System.out.println(Thread.currentThread().getName());
        }
    }

在这里插入图片描述
我们发现根本就没有。因为我们的“第一个线程是一个死循环”根本都执行不到main线程,所以没法打印出main线程名。
因为这根本就没有新创建一个线程,而是把我们的run方法加载到主方法来执行了而已,就和我们平常的方法调用没有什么区别。真正开启新线程的方法是start,我们来看下start方法在这里插入图片描述
译文使该线程开始执行;Java虚拟机调用这个线程的run方法。 结果是两个线程并发运行:当前线程(从对start方法的调用中返回)和另一个线程(执行其run方法)。 多次启动一个线程是不合法的。特别是,线程在完成执行后可能不会重新启动。
大致意思是从这里出现了两个线程,一个是我们覆写的run线程一个调用start线程的main线程。
那么到这里我们已经解释了为什么不能直接run来实现创建线程。
那既然不能用run方法直接创建线程,需要使用start方法,而创建的新线程中需要执行的操作(也就是要干的事)是放到覆写之后的run方法中的,那我们的大概思路已经清晰了。
start方法启动线程来调用run方法中新建线程要执行的操作。
也就是本文的主题,那么start方法是如何调用run方法的。
上源码:在这里插入图片描述
我们看start方法的核心应该是group.add方法或者start(0)方法,我们打开group.add,发现是将线程保存到数组中,但是没有发现run的痕迹,于是我们再来看start(0)在这里插入图片描述
native修饰的一个私有方法,那么被native修饰有什么意义呢?在这里插入图片描述
native:native是一个计算机函数,一个Native Method就是一个Java调用非Java代码的接口。方法的实现由非Java语言实现,比如C或C++。
Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口)。我们知道了这个方法是java调用底层c的接口。该过程由JVM底层来完成。
那么Java是如何通过JNI和c语言通信的呢?
通过RegisterNatives方法,我们来看Thread的该方法.
Make sure registerNatives is the first thing <clinit> does.
确保registernative是客户端所做的第一件事。在这里插入图片描述static代码块修饰的方法,再方法区类加载到JVM的时候就被加载,该方法可以调用JNI接口。注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,==所有操作本地线程的本地方法都是由它注册的 ==. 这个方法放在一个 static 语句块中,这就表明,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法
至此,大致线程大致就是start()–>JVM–>run()
我们继续往下看,registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作。

 JNIEXPORT void JNICALL 
 Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ 
   (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); 
 } 
 static JNINativeMethod methods[] = { 
    {"start0", "()V",(void *)&JVM_StartThread}, 
    {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, 
	 {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, 
	 {"suspend0","()V",(void *)&JVM_SuspendThread}, 
	 {"resume0","()V",(void *)&JVM_ResumeThread}, 
	 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, 
	 {"yield", "()V",(void *)&JVM_Yield}, 
	 {"sleep","(J)V",(void *)&JVM_Sleep}, 
	 {"currentThread","()" THD,(void *)&JVM_CurrentThread}, 
	 {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, 
	 {"interrupt0","()V",(void *)&JVM_Interrupt}, 
	 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, 
	 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, 
	 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, 
	 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 
 };

我们找到了start(0),Java 线程调用 start 方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢?

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
	…
	 native_thread = new JavaThread(&thread_entry, sz);

这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry

 static void thread_entry(JavaThread* thread, TRAPS) { 
    HandleMark hm(THREAD); 
	 Handle obj(THREAD, thread->threadObj()); 
	 JavaValue result(T_VOID); 
	 JavaCalls::call_virtual(&result,obj, 
	 KlassHandle(THREAD,SystemDictionary::Thread_klass()), 
	 vmSymbolHandles::run_method_name(), 
 vmSymbolHandles::void_method_signature(),THREAD); 
 }

可以看到调用了 vmSymbolHandles::run_method_name 方法,这是在 vmSymbols.hpp 用宏定义的:

class vmSymbolHandles: AllStatic {template(run_method_name,"run")}

至此完善一下整个过程如下:
在这里插入图片描述
完毕!

  • 34
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴成伟0122

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值