likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 23
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

前言

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

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

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

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

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

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

摘要

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

简介

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

案例演示

如何创建线程?

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类 VS 实现Runnable接口 | 多线程篇(二)

代码解析:

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

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

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

改进建议:

  如上代码入门还是必须要掌握,但掌握了之后,再这么写就不够优雅,如下是我对代码的高阶玩法,同学们这波得会!

  1. 使用@Override注解:在run()方法上使用@Override注解可以提高代码的可读性,并且如果run()方法的签名与Thread类的run()方法不匹配,编译器会给出错误提示。

  2. 线程安全:如果MyThread类中包含共享资源,需要考虑线程安全问题,比如使用同步机制来避免竞态条件。

  3. 异常处理:在run()方法中执行的代码可能会抛出异常,应该考虑添加异常处理逻辑,以避免线程因未捕获的异常而意外终止。

  4. 线程的命名:可以通过setName()方法为线程设置一个有意义的名称,这样可以在调试时更容易识别线程。

  5. 资源清理:如果线程使用了一些资源(如文件句柄、网络连接等),应该在线程执行完毕后进行适当的清理。

  通过这些改进,可以使线程类更加健壮和易于维护,这也是我们作为开发者必须要精进的地方之一。

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();
    }
}

结果展示:

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

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

代码解析:

  根据我如上给出的示例代码,这里我简单做个解析吧,这段代码展示了如何通过实现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();
    }
}

结果展示:

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

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

代码解析:

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

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

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

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

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

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

优缺点分析

  • 继承Thread类:简单直观,但限制了继承其他类的能力。
  • 实现Runnable接口:更灵活,避免了单继承限制,推荐使用。
  • 使用Callable和Future:可以获取线程执行结果,但增加了编程复杂性。

继承Thread类 VS 实现Runnable接口

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

继承Thread类

  1. 简单性:直接继承Thread类并重写run()方法是一种直观且简单的线程创建方式。
  2. 限制性:由于Java不支持多重继承,如果类已经继承了另一个类,则不能继承Thread类。

实现Runnable接口

  1. 灵活性:实现Runnable接口允许类继承另一个类的同时,还能具备多线程执行的能力。
  2. 推荐方式:由于继承的限制,Java官方推荐使用Runnable接口来创建线程。

优缺点分析

  • 继承Thread类

    • 优点:简单直观,适合简单的线程创建。
    • 缺点:由于Java的单继承特性,限制了类的继承能力。
  • 实现Runnable接口

    • 优点:提供了更大的灵活性,允许类继承其他类的同时实现多线程。
    • 缺点:需要额外创建Thread对象,代码稍显复杂。

应用场景

  • 当类结构简单,且没有继承其他类的需求时,继承Thread类是一个快速的选择。
  • 当需要实现多继承或者类已经继承了其他类时,实现Runnable接口是更好的选择。

结论

  尽管继承Thread类在某些简单场景下足够使用,但实现Runnable接口提供了更大的灵活性和扩展性。因此,除非有特别的理由,通常推荐使用Runnable接口来创建和管理线程。这样做不仅可以避免继承的限制,还可以使代码更加模块化和易于维护。

案例分析

  通过上述这三种代码示例,我们可以看到Java提供了灵活的线程创建方式。继承Thread类是最基本的方法,但这种方式存在单继承的限制。实现Runnable接口是一种推荐方式,它允许开发者在不继承Thread的情况下创建线程。而使用Callable和Future则可以创建有返回值的线程任务,这在需要线程执行结果的场景中非常有用。总而言之,这需要根据具体的场景来选择不同的创建方式。

应用场景

  如下我来给同学们科普下,运用多线程的一些常见场景,案例列举如下:

  1. Web服务器:处理多个客户端请求。
  2. 数据库操作:并行执行多个数据库查询。
  3. 图形用户界面:响应用户操作,同时进行后台处理。

类代码方法介绍

  • Thread类:提供了线程的基本管理功能。
  • Runnable接口:定义了线程执行的方法run()
  • Callable接口:允许线程任务返回结果。
  • Future接口:提供了检查计算是否完成的方法,并可获取结果。

测试用例

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

测试代码

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

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();
    }
}

测试结果

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

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

测试代码解析

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

  这段代码是一个多线程测试程序,它演示了三种不同的线程创建和启动方式:继承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多线程的几种创建方式后,我们不难发现,多线程不仅仅是提升程序性能的工具,更是一门艺术,一种能够让我们的代码提高并高效运行效率。通过本文的学习,我们不仅回顾了线程的基本概念,如线程与进程的区别、线程的生命周期和优先级,还深入研究了Java中线程的创建和管理方法。

继承Thread类

  继承Thread类的方式简单直接,它让线程的创建变得容易,但这种简单性背后隐藏着局限——Java的单继承特性限制了我们的选择。这就像是一条只能通往一个方向的道路,虽然清晰,但缺乏灵活性。

实现Runnable接口

  相比之下,实现Runnable接口则像是打开了一扇门,让我们的类在继承其他类的同时,也能成为线程。这种方式更加灵活,也更符合Java的设计哲学。它告诉我们,即使在有限的条件下,我们也能通过创造性的方法找到解决方案。

使用Callable和Future

  而CallableFuture的结合使用,则为我们提供了一种全新的视角——线程不仅能执行任务,还能返回结果。这就像是赋予了线程新的生命,让它们不仅能工作,还能“思考”并给出答案。

应用场景

  在实际开发中,无论是处理Web服务器的并发请求,还是进行数据库的并行查询,或是在图形用户界面中响应用户操作的同时进行后台处理,多线程都扮演着至关重要的角色。

优缺点分析

  每种线程创建方式都有其适用的场景和存在的价值。继承Thread类虽然简单,但限制了类的继承能力;实现Runnable接口虽然灵活,却需要我们手动创建Thread对象;而CallableFuture虽然强大,但也带来了编程的复杂性。

全文小结

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

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

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

... ...

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

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

往期推荐

转载自:https://juejin.cn/post/7385966245587632180
评论
请登录