多线程 | 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 接口 的方法应该更符合软件的设计原则。当然了,在项目中不建议显式的创建线程,更推荐的是使用线程池。

相关文章
|
26天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
53 12
|
24天前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
51 5
|
24天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
1天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
1天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
29天前
|
Java C# Python
线程等待(Thread Sleep)
线程等待是多线程编程中的一种同步机制,通过暂停当前线程的执行,让出CPU时间给其他线程。常用于需要程序暂停或等待其他线程完成操作的场景。不同语言中实现方式各异,如Java的`Thread.sleep(1000)`、C#的`Thread.Sleep(1000)`和Python的`time.sleep(1)`。使用时需注意避免死锁,并考虑其对程序响应性的影响。
|
28天前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
1月前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
118 11
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
32 3
|
2月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
56 2