了解Java线程的start方法如何回调run方法

本文详细解析了Java中创建线程的两种方式:继承Thread类和实现Runnable接口,并深入探讨了start()方法如何触发run()方法执行的过程。此外,还介绍了Java线程的底层实现原理。

面试中可能会被问到为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

Java 创建线程的方法

实际上,创建线程最重要的是提供线程函数(回调函数),该函数作为新创建线程的入口函数,实现自己想要的功能。Java 提供了两种方法来创建一个线程:

继承 Thread 类

class MyThread extends Thread{ 
    public void run() {    
        System.out.println("My thread is started."); 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5

实现该继承类的 run 方法,然后就可以创建这个子类的对象,调用 start 方法即可创建一个新的线程:

MyThread myThread = new MyThread(); 
myThread.start();
  • 1
  • 2

实现 Runnable 接口

class MyRunnable implements Runnable{ 
    public void run() { 
        System.out.println("My runnable is invoked."); 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5

实现 Runnable 接口的类的对象可以作为一个参数传递到创建的 Thread 对象中,同样调用 Thread#start 方法就可以在一个新的线程中运行 run 方法中的代码了。

Thread myThread = new Thread( new MyRunnable()); 
myThread.start();
  • 1
  • 2

可以看到,不管是用哪种方法,实际上都是要实现一个 run 方法的。 该方法本质是上一个回调方法。由 start 方法新创建的线程会调用这个方法从而执行需要的代码。 从后面可以看到,run 方法并不是真正的线程函数,只是被线程函数调用的一个 Java 方法而已,和其他的 Java 方法没有什么本质的不同。

Java 线程的实现

从概念上来说,一个 Java 线程的创建根本上就对应了一个本地线程(native thread)的创建,两者是一一对应的。 问题是,本地线程执行的应该是本地代码,而 Java 线程提供的线程函数是 Java 方法,编译出的是 Java 字节码,所以可以想象的是, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。

以下是 Thread#start 方法的示例:

public synchronized void start() { 
    …
    start0(); 
    …
}
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到它实际上调用了本地方法 start0, 该方法的声明如下:

private native void start0();
  • 1

Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的 . 这个方法放在一个 static 语句块中,这就表明,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。

private static native void registerNatives(); 
static{ 
  registerNatives(); 
}
  • 1
  • 2
  • 3
  • 4

本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如代码清单 1 所示。

清单1

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}, 
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

到此,可以容易的看出 Java 线程调用 start 的方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢。实际上,我们需要的是(或者说 Java 表现行为)该方法最终要调用 Java 线程的 run 方法,事实的确如此。 在 jvm.cpp 中,有如下代码段:

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

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

清单2

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); 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

class vmSymbolHandles: AllStatic { 
   …
    template(run_method_name,"run") 
   …
}
  • 1
  • 2
  • 3
  • 4
  • 5

至于 run_method_name 是如何声明定义的,因为涉及到很繁琐的代码细节,本文不做赘述。感兴趣的读者可以自行查看 JVM 的源代码。

图. Java 线程创建调用关系图

这里写图片描述

start() 创建新进程 
run() 没有

### Java线程中 `start()` 方法调用 `run()` 方法的机制 `Thread.start()` 方法Java 中启动新线程的关键入口,它负责通知 JVM 创建并启动一个新的线程。尽管表面上看起来简单,但实际上它的内部实现非常复杂,涉及到底层的操作系统交互以及 JNI(Java Native Interface)的支持。 #### 1. `start()` 方法的作用 当调用 `thread.start()` 时,JVM 并不会立即执行 `run()` 方法中的代码,而是通过本地方法触发操作系统的线程创建过程[^1]。具体来说: - **线程创建阶段**:`start()` 方法会请求操作系统分配新的线程资源,并将其加入到调度队列中。 - **线程初始化阶段**:在成功创建线程之后,JVM 将准备执行目标线程的任务逻辑。 - **任务执行阶段**:一旦新线程被调度器选中运行,JVM 自动调用该线程实例的 `run()` 方法作为其初始任务。 需要注意的是,开发者无需手动调用 `run()` 方法;如果直接调用 `thread.run()`,那么实际上只是在当前线程上下文中顺序执行了 `run()` 方法的内容,而不是开启真正的并发行为[^2]。 #### 2. 底层实现细节 以下是关于 `start()` 和 `run()` 方法之间关系的一些重要技术要点: - **JNI 层面**:`start()` 方法最终会映射到底层 C/C++ 实现,在那里完成实际的新线程建立工作。此部分通常依赖于平台特定 API 来管理硬件级线程支持。 - **线程状态转换**:从用户视角来看,调用了 `start()` 后,线程的状态由 NEW 变更为 RUNNABLE 或 BLOCKED (取决于是否有锁竞争)[^3]。此时即使还没有被执行,也已经具备随时可运行条件。 - **同步与互斥处理**:假如多个线程尝试同时修改共享数据结构,则可能需要用到 synchronized 关键字来保护临界区代码片段以防竞态发生[^3]。这一步骤虽然不直接影响 start-run 流程本身,但在设计健壮程序时非常重要。 #### 示例代码展示 下面给出一段简单的例子说明如何正确使用 `start()` 方法间接触达 `run()` 函数体: ```java public class MyRunnable implements Runnable { public void run() { System.out.println(Thread.currentThread().getName()+" is running."); } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new MyRunnable(), "t1"); // 使用 start() 开启独立线程 t1.start(); // 主线程继续自己的流程... Thread.sleep(50); System.out.println("Main thread finished."); } } ``` 在这个案例里,“t1”代表我们自定义的一个子线程对象。“is running.”消息来自另一个 CPU 上下文环境下的打印语句——即证明确实形成了异步效果! --- ### 总结 综上所述,`Thread.start()` 不仅是一个普通的函数调用那么简单,它是整个 Java线程体系的基础构件之一。通过对原生接口封装后的抽象层次提升使得我们可以更方便快捷地构建跨平台应用软件解决方案的同时还隐藏了许多繁琐低级别的细节问题给程序员带来便利性体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值