多线程 | Thread 和 Runnable 执行流程的差异

简介: 多线程 | Thread 和 Runnable 执行流程的差异

上图来自网络

一、查看源码

       在 Java 中创建自定义线程通常有两种方法,一种方法是继承 Thread 类,另外一种方法是实现 Runnable 接口。

       无论是使用 继承 Thread 类 的方式,还是 实现 Runnable 接口 的方式,本质上是没有差别的。因为 Thread 本身也是实现了 Runnable 接口的。查看 Thread 类的定义,定义如下:

class Thread implements Runnable

       如果使用继承 Thread 类的方式,通常,我们会定义一个 MyThread 类来继承 Thread 类,并重写 run 方法,然后 new 一个 MyThread 实例,通过这个实例调用 start 方法,让 JVM 来启动这个线程。


       如果使用实现 Runnable 接口的方式,通常,我们也会定义一个 MyRunnable 类来实现 Runnable 接口,并重写 run 方法,然后 new 一个 MyRunnable 实例,但是 MyRunnable 实例是并没有 start 方法的。因此,仍然需要 new 一个 Thread 类的实例,将 MyRunnable 实例当作参数传递给 Thread 的构造方法,最后同样通过 Thread 实例的 start 方法,让 JVM 来启动线程。


       这里来看一下,Thread 类的构造方法,Thread 类的构造方法有若干个,现在主要来看它的 无参构造方法 和 传递 Runnable 类型的构造方法,代码如下:

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

       两个构造函数,都调用了 init 方法。传递 Runnable 类型参数的构造方法比无参构造方法多了一个 target 传递给 init 方法。来看 init 方法的代码:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    // ...
    this.target = target;
    // ...
}

      对于 target 参数给出的注释为:

the object whose run() method gets called

       表示调用 run() 方法的对象,接着来看一下 Thread 类中的 run 方法,代码如下:

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

    代码里会判断 target 是否不为 null,不为 null 则调用 target.run。再来看该方法的注释:

If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.
Subclasses of Thread should override this method.

       如果此线程是使用单独的 Runnable run 对象构造的,则调用该 Runnable 对象的 run方法;否则,此方法不执行任何操作并返回。接下来通过实例来调试分析一下。


二、实例分析

       先来写一个继承 Thread 的类的方式来进行观察,代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.start();
        System.out.println(Thread.currentThread().getName());
    }
}

       此时,分别在 Thread 类的 run 方法下断,在 MyThread 的 run 方法下断,然后调试运行,代码直接中断在了 MyThread 类的 run 方法处。调试运行代码发现,并没有中断在 Thread 类的 run 方法处,按照面向对象的设计来说,子类方法覆盖了父类的方法,当前对象是 子类的实例,则应该直接调用 子类的方法。


       再来写一个实现 Runnable 的代码,代码如下:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        Runnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    }
}

       同样的操作,在 Thread 类的 run 方法和 MyRunnable 的 run 方法处分别设置断点,然后调试运行,代码首先中断在了 Thread 类的 run 方法,然后继续运行,代码又中断在了 MyRunnable 的 run 方法。此时观察调用栈会看到 MyRunnable 的 run 方法是 Thread 类的 run 方法调用的。


三、总结

       通过查看源码以及实例代码调试发现,实现 Runnable 接口 在代码执行的流程上比 继承 Thread 类 的流程要稍微复杂些。不过 实现 Runnable 接口 的方法应该更符合软件的设计原则。当然了,在项目中不建议显式的创建线程,更推荐的是使用线程池。

相关文章
|
13天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
16天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
16天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
28 2
|
16天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2
|
16天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
26 1
|
2月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
50 2
面试官:说说停止线程池的执行流程?
|
2月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
【多线程面试题 二】、 说说Thread类的常用方法
Thread类的常用方法包括构造方法(如Thread()、Thread(Runnable target)等)、静态方法(如currentThread()、sleep(long millis)、yield()等)和实例方法(如getId()、getName()、interrupt()、join()等),用于线程的创建、控制和管理。
|
3月前
|
SQL 机器学习/深度学习 算法
【python】python指南(一):线程Thread
【python】python指南(一):线程Thread
45 0
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
39 1
C++ 多线程之初识多线程