如何创建线程?继承Thread类 VS 实现Runnable接口 | 多线程篇(二)

722 阅读15分钟
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

一、前言

  在上一期的《深入Java多线程:基础篇,你必须掌握!| 多线程篇(一)》中,我们共同学习了Java多线程的基础知识,包括但不限于线程的创建、生命周期、以及线程同步等概念。我相信,通过那一期的学习,大家已经对多线程有了初步的理解和掌握。

如下是上期的内容大纲,同学们自己查缺补漏。

  • 线程与进程
    • 何为线程?
    • 何为进程?
    • 二者区别
  • 线程的生命周期
    • 概念
    • 线程生命周期
    • 进程生命周期
    • 软件工程中的生命周期
  • 线程优先级
    • 概念
    • 特点
    • 如何设置线程优先级?
    • setPriority()方法详解
    • 应用场景
  • 线程优缺点
  • 案例演示
  • 案例代码解析
  • 案例实测

  正如古人云:“温故而知新,可以为师矣!”,复习旧知识是学习新知识的重要步骤。如果你已经对上期内容了如指掌,那么恭喜你,你的自学能力非常强,这是成为一名优秀程序员的重要素质。

  现在,让我们带着这份自信,继续深入Java多线程。在这一篇章中,我们将探讨更深入的主题,包括但不限于:

  • 继承 Thread 类:
    • 创建线程的一种基本方式。
  • 实现 Runnable 接口:
    • 创建线程的推荐方式,可以避免单继承限制。
  • 使用 Callable 和 Future:
    • 实现有返回值的线程任务。

  通过这一期的学习,你将能够更加深入地理解多线程编程的复杂性和强大功能,为你的编程之路添砖加瓦。让我们一起开启这段新的学习旅程,让你的代码在多线程的加持下飞速运行!这也是学习多线程的意义。

二、摘要

  本文旨在深入探讨Java中线程的创建和管理方法,通过实际代码示例和案例分析,为开发者提供一种易于理解和应用的线程处理策略。全文将涵盖线程的基本概念、创建方式、优缺点分析以及实际应用场景。

三、简介

  在当前的软件开发中,多线程编程是提高程序性能和响应速度的关键技术之一,这也是在你项目中,非常有挑战性的内容之一,让你如何优化代码提高并发效率,这你就可以考虑用它。不过,对于Java,它本身也提供了多种线程创建和管理的方法,以适应不同的应用场景,这也是需要去甄别的。

四、正文

i.如何创建线程?

1.继承Thread类

  首先,我们我先通过最简单且易上手的方式来创建线程,看看这种方式你能不能立马学会,注意看!

示例代码如下:

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-06-26 10:36
 */
public class MyThread extends Thread {
    public void run() {
        System.out.println("线程执行啦!");
    }
}
/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-06-26 10:36
 */
public class ThreadExample {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

结果展示:

  根据如上代码,本地实测结果展示如下:

代码解析:

  根据我如上给出的示例代码,这里我简单做个解析:如上代码展示了如何通过继承Thread类来创建一个简单的Java线程。下面是我对该代码的解释和一些改进建议:

  1. MyThread类:这个类继承自Java的Thread类,重写了run()方法。这是线程执行的入口点,当调用start()方法时,run()方法中的代码将被执行。

  2. ThreadExample类:这个类包含了main方法,它是程序的入口点。在main方法中,创建了MyThread的一个实例,并调用了start()方法来启动线程。

2.实现Runnable接口

  接着,我们便来通过第二种方式来创建线程,看看这种方式有何不同之处,注意看!示例代码如下:

/**
 * 实现Runnable接口的自定义线程类
 *
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-06-26 10:36
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("使用MyRunnable创建线程!run啦!");
    }

    public static void main(String[] args) {
        // 创建MyRunnable的实例
        MyRunnable myRunnable = new MyRunnable();

        // 使用该实例创建Thread对象
        Thread thread = new Thread(myRunnable);

        // 启动线程
        thread.start();
    }
}

结果展示:

  根据如上代码,本地实测结果展示如下:

代码解析:

  根据我如上给出的示例代码,这里我简单做个解析吧,这段代码展示了如何通过实现Runnable接口来创建一个线程。以下是对代码的解释:

  1. MyRunnable类:这个类实现了Runnable接口,这意味着它必须提供run()方法的具体实现。这是线程执行的入口点。

  2. main方法main方法是程序的入口点。在这个方法中,创建了MyRunnable的一个实例,并用它来创建一个新的Thread对象。然后调用start()方法来启动线程。

3.使用Callable和Future

package com.secf.service.action.hpy.test;

import java.util.concurrent.*;

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-06-26 10:36
 */
public class MyCallable implements Callable<Integer> {
    public Integer call() {
        // 线程执行的代码,并返回结果
        return 20240626;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new MyCallable());
        System.out.println("Result: " + future.get());
        executor.shutdown();
    }
}

结果展示:

  根据如上代码,本地实测结果展示如下:

代码解析:

  根据我如上给出的示例代码,这里我简单做个解析:这段代码展示了如何使用Callable接口和Future类来创建一个可以返回结果的线程任务。以下是对代码的解释:

  1. MyCallable类:这个类实现了Callable接口,这意味着它必须提供call()方法的具体实现。与Runnable接口不同,Callable的任务可以返回一个结果,并且可以抛出异常。

  2. call()方法:这是线程执行的入口点,它返回一个Integer类型的结果。在这个例子中,它简单地返回了一个硬编码的值20240626

  3. main方法main方法是程序的入口点。在这个方法中,创建了一个ExecutorService实例来管理线程池,然后使用submit()方法提交MyCallable任务。通过Future对象,程序能够获取任务的返回结果,并在任务完成后关闭线程池。

  最后,这里附上部分相关注解源码截图,这里我就简单给附上,感兴趣的同学可以扒扒源码,深入去学习下开源框架的设计构思及理念,这也是掌握一个架构的核心目标,但是基础一般或者零基础的同学,建议先从使用上深入,而不是一口吃掉一个胖子,得不偿失。

ii.继承Thread类 VS 实现Runnable接口

  在如上案例演示,我们也基本对创建线程的几种方式有了一定的了解,虽然说还有很多别的方式,这里我就暂时不先普及了。学习如上,我们得知创建线程的两种主要方式是继承Thread类和实现Runnable接口。那么?针对这两最为经典的方式,二者有何同异?这两种方法又各有其特点和适用场景?以下是我对这两种方式的比较分析:

a.继承Thread类

基本概念

  Thread类是Java中用于创建和管理线程的类。通过继承Thread类并重写其run()方法,可以定义线程的执行逻辑。每个Thread对象代表一个线程,调用start()方法后,线程开始执行run()方法中的代码。

1.优点
  1. 简单直观:继承Thread类的方式非常直观,尤其适合快速开发简单的多线程程序。
  2. 直接使用Thread类的方法:继承Thread类后,可以直接使用Thread类提供的各种方法,如getName()setPriority()等,方便线程的管理和控制。
2.缺点
  1. 不支持多重继承:Java不支持多重继承,如果一个类已经继承了其他类,就不能再继承Thread类,这限制了这种方法的灵活性。
  2. 耦合性高:继承Thread类将线程的行为与线程的实现紧密耦合,降低了代码的可重用性和扩展性。

b.实现Runnable接口

基本概念

  Runnable接口是一个函数式接口,只有一个run()方法。通过实现Runnable接口,并将其作为线程的任务,可以将线程的行为与实现分离。这种方法更灵活,特别是在需要创建多个线程来执行相同任务的情况下。

1.优点
  1. 解耦:实现Runnable接口将线程的行为与其实现解耦,增强了代码的灵活性和可重用性。
  2. 支持多重继承:由于Java支持实现多个接口,使用Runnable接口不会限制类的继承关系,使得代码更易于扩展。
  3. 适合线程池Runnable接口非常适合与线程池一起使用,因为线程池通常会管理多个线程执行相同或不同的任务。
2.缺点
  1. 间接访问线程方法:使用Runnable接口时,无法直接调用Thread类的方法,需要通过Thread.currentThread()来访问线程的相关信息,这可能会增加代码的复杂性。
  2. 相对复杂:与直接继承Thread类相比,实现Runnable接口需要额外的步骤(如创建Thread对象),对于简单的线程应用可能显得不够直观。

iii.两种方式的比较

特性继承Thread实现Runnable接口
灵活性低,无法多重继承高,支持多重继承
解耦性低,线程行为与实现紧密耦合高,线程行为与实现分离
适合场景简单的、多线程的、需要直接控制线程的应用复杂的、多任务的、需要与线程池结合的应用
代码复杂性低,继承类后直接调用start()即可相对较高,需要实现接口并创建Thread对象
线程管理方法直接访问Thread类的方法,如getName()需要通过Thread.currentThread()间接访问
线程任务的复用任务和线程绑定,不易复用任务独立于线程,可以复用
与线程池的结合不适合与线程池结合,通常直接管理线程非常适合与线程池结合,如使用ExecutorService

iiii.实际开发中的选择

1. 何时选择继承Thread

  • 简单任务:当线程的任务较为简单且不需要复用时,继承Thread类是最直接和简便的选择。
  • 需要直接操作线程:如果需要频繁使用Thread类的各种方法,如设置优先级、检查线程状态等,继承Thread类可以让代码更加简洁和直观。
  • 少量线程:如果程序只需要创建少量的线程,且不涉及复杂的线程管理,继承Thread类的方式可以快速实现多线程功能。

2. 何时选择实现Runnable接口

  • 任务与线程解耦:当需要将任务的执行与线程的管理分离时,实现Runnable接口是更好的选择。这样可以使代码更加模块化和可重用。
  • 多任务复用:如果需要在多个线程中执行相同的任务,使用Runnable接口可以更方便地复用任务逻辑,而无需重复创建多个线程类。
  • 与线程池结合使用:在现代多线程编程中,线程池是提高效率和管理线程资源的重要工具。实现Runnable接口的任务对象非常适合在线程池中使用,如通过ExecutorService来管理任务的执行。

五、测试用例

  根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

1.测试代码

  测试代码结合如上三种创建方式进行合并演示,示例代码如下:

package com.secf.service.action.hpy.test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-06-26 10:36
 */
public class ThreadTest {
    public static void main(String[] args) {
        // 测试继承Thread类
        MyThread thread1 = new MyThread();
        thread1.start();

        // 测试实现Runnable接口
        Thread thread2 = new Thread(new MyRunnable());
        thread2.start();

        // 测试使用Callable和Future
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new MyCallable());
        try {
            System.out.println("Callable returned: " + future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();
    }
}

2.测试结果

  根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

3.测试代码解析

  接着我将对上述代码进行详细的一个逐句解读,希望能够帮助到同学们,能以更快的速度对其知识点掌握学习,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,所以如果有基础的同学,可以略过如下代码分析步骤,然而没基础的同学,还是需要加强对代码的理解,方便你深入理解并掌握其常规使用。

  这段代码是一个多线程测试程序,它演示了三种不同的线程创建和启动方式:继承Thread类、实现Runnable接口以及使用CallableFuture。以下是对代码的简要说明:

  1. MyThread类:继承自Thread类,重写了run()方法,但代码中没有给出这个类的实现。在main方法中,创建了MyThread的实例并启动了线程。

  2. MyRunnable类:实现了Runnable接口,重写了run()方法,同样代码中没有给出这个类的实现。在main方法中,创建了MyRunnable的实例,并用它创建了一个新的Thread对象,然后启动了线程。

  3. MyCallable类:实现了Callable接口,重写了call()方法,返回一个Integer类型的结果。在main方法中,创建了一个单线程的ExecutorService,提交了MyCallable任务,并获取了返回结果。

  4. 异常处理:使用try-catch块来捕获和处理future.get()可能抛出的InterruptedExceptionExecutionException

  5. 线程池关闭:在所有线程任务提交并执行完毕后,调用executor.shutdown()来关闭线程池。

六、全文小结

  通过本文的学习,我们不仅掌握了Java多线程的创建和管理,更重要的是,我们学会了如何根据不同的应用场景,选择最合适的线程模型。我们了解到,尽管多线程能显著提升程序的性能,但在使用时也要注意线程安全和资源管理,避免并发问题的发生。

  在这个多核CPU和高并发需求日益增长的时代,掌握多线程编程技术,就像是拥有了打开高效编程大门的钥匙。让我们带着这份自信和知识,继续在编程的道路上探索和前进,用多线程让我们的代码飞速运行,创造出更加出色的系统/软件/App等应用。

七、全文总结

  在深入学习Java多线程的几种创建方式后,我们不难发现,多线程不仅仅是提升程序性能的工具,更是一门艺术,一种能够让我们的代码提高并高效运行效率。通过本文的学习,我们不仅回顾了线程的基本概念,如线程与进程的区别、线程的生命周期和优先级,还深入研究了Java中线程的创建和管理方法。

  同学, 请记住,学习是一个永无止境的过程,而多线程编程,只是这个过程中的一部分。让我们保持好奇心,不断探索,不断学习,因为每一次深入学习,都会让我们离成为一名优秀程序员的目标更近一步。

至此,感谢阅读本文,如果你觉得有所收获,不妨点赞、关注和收藏,以支持bug菌继续创作更多高质量的技术内容。同时,欢迎加入我的技术社区,一起学习和成长。   

学无止境,探索无界,期待在技术的道路上与你再次相遇。咱们下期拜拜~~

八、往期推荐

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!