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

相关文章
|
5天前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
10 0
|
17天前
|
Java
解析Java线程池:参数详解与执行流程
解析Java线程池:参数详解与执行流程
15 1
|
19天前
|
存储 设计模式 安全
C++一分钟之-并发编程基础:线程与std::thread
【6月更文挑战第26天】C++11的`std::thread`简化了多线程编程,允许并发执行任务以提升效率。文中介绍了创建线程的基本方法,包括使用函数和lambda表达式,并强调了数据竞争、线程生命周期管理及异常安全等关键问题。通过示例展示了如何用互斥锁避免数据竞争,还提及了线程属性定制、线程局部存储和同步工具。理解并发编程的挑战与解决方案是提升程序性能的关键。
37 3
|
21天前
|
Java
Java中,有两种主要的方式来创建和管理线程:`Thread`类和`Runnable`接口。
【6月更文挑战第24天】Java创建线程有两种方式:`Thread`类和`Runnable`接口。`Thread`直接继承受限于单继承,适合简单情况;`Runnable`实现接口可多继承,利于资源共享和任务复用。推荐使用`Runnable`以提高灵活性。启动线程需调用`start()`,`Thread`直接启动,`Runnable`需通过`Thread`实例启动。根据项目需求选择适当方式。
26 2
|
26天前
|
Java 开发者
JAVA多线程初学者必看:为何选择继承Thread还是Runnable,这其中有何玄机?
【6月更文挑战第19天】在Java中创建线程,可选择继承Thread类或实现Runnable接口。继承Thread直接运行,但限制了多重继承;实现Runnable更灵活,允许多线程共享资源且利于代码组织。推荐实现Runnable接口,以保持类的继承灵活性和更好的资源管理。
|
26天前
|
Java 开发者
告别单线程时代!Java 多线程入门:选继承 Thread 还是 Runnable?
【6月更文挑战第19天】在Java中,面对多任务需求时,开发者可以选择继承`Thread`或实现`Runnable`接口来创建线程。`Thread`继承直接但限制了单继承,而`Runnable`接口提供多实现的灵活性和资源共享。多线程能提升CPU利用率,适用于并发处理和提高响应速度,如在网络服务器中并发处理请求,增强程序性能。不论是选择哪种方式,都是迈向高效编程的重要一步。
|
26天前
|
Java C++ 开发者
线程创建的终极对决:Thread 类 VS Runnable 接口,你站哪边?
【6月更文挑战第19天】在Java多线程编程中,通过`Thread`类直接继承或实现`Runnable`接口创建线程各有优劣。`Thread`方式简洁但不灵活,受限于Java单继承;`Runnable`更灵活,适合资源共享和多接口实现,提高代码可维护性。选择取决于项目需求和设计原则,需权衡利弊。
|
5天前
|
存储 安全 Java
Java面试题:如何在Java应用中实现有效的内存优化?在多线程环境下,如何确保数据的线程安全?如何设计并实现一个基于ExecutorService的任务处理流程?
Java面试题:如何在Java应用中实现有效的内存优化?在多线程环境下,如何确保数据的线程安全?如何设计并实现一个基于ExecutorService的任务处理流程?
10 0
|
26天前
|
Java
揭秘!为何Java多线程中,继承Thread不如实现Runnable?
【6月更文挑战第19天】在Java多线程中,实现`Runnable`比继承`Thread`更佳,因Java单继承限制,`Runnable`可实现接口复用,便于线程池管理,并分离任务与线程,提高灵活性。当需要创建线程或考虑代码复用时,实现`Runnable`是更好的选择。
|
4天前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
17 1