如何创建线程?继承Thread类 VS 实现Runnable接口 | 多线程篇(二)
环境说明: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
类来创建一个简单的Java线程。下面是我对该代码的解释和一些改进建议:
-
MyThread类:这个类继承自Java的
Thread
类,重写了run()
方法。这是线程执行的入口点,当调用start()
方法时,run()
方法中的代码将被执行。 -
ThreadExample类:这个类包含了
main
方法,它是程序的入口点。在main
方法中,创建了MyThread
的一个实例,并调用了start()
方法来启动线程。
改进建议:
如上代码入门还是必须要掌握,但掌握了之后,再这么写就不够优雅,如下是我对代码的高阶玩法,同学们这波得会!
-
使用@Override注解:在
run()
方法上使用@Override
注解可以提高代码的可读性,并且如果run()
方法的签名与Thread
类的run()
方法不匹配,编译器会给出错误提示。 -
线程安全:如果
MyThread
类中包含共享资源,需要考虑线程安全问题,比如使用同步机制来避免竞态条件。 -
异常处理:在
run()
方法中执行的代码可能会抛出异常,应该考虑添加异常处理逻辑,以避免线程因未捕获的异常而意外终止。 -
线程的命名:可以通过
setName()
方法为线程设置一个有意义的名称,这样可以在调试时更容易识别线程。 -
资源清理:如果线程使用了一些资源(如文件句柄、网络连接等),应该在线程执行完毕后进行适当的清理。
通过这些改进,可以使线程类更加健壮和易于维护,这也是我们作为开发者必须要精进的地方之一。
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
接口来创建一个线程。以下是对代码的解释:
-
MyRunnable类:这个类实现了
Runnable
接口,这意味着它必须提供run()
方法的具体实现。这是线程执行的入口点。 -
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
类来创建一个可以返回结果的线程任务。以下是对代码的解释:
-
MyCallable类:这个类实现了
Callable
接口,这意味着它必须提供call()
方法的具体实现。与Runnable
接口不同,Callable
的任务可以返回一个结果,并且可以抛出异常。 -
call()方法:这是线程执行的入口点,它返回一个
Integer
类型的结果。在这个例子中,它简单地返回了一个硬编码的值20240626
。 -
main方法:
main
方法是程序的入口点。在这个方法中,创建了一个ExecutorService
实例来管理线程池,然后使用submit()
方法提交MyCallable
任务。通过Future
对象,程序能够获取任务的返回结果,并在任务完成后关闭线程池。
最后,这里附上部分相关注解源码截图,这里我就简单给附上,感兴趣的同学可以扒扒源码,深入去学习下开源框架的设计构思及理念,这也是掌握一个架构的核心目标,但是基础一般或者零基础的同学,建议先从使用上深入,而不是一口吃掉一个胖子,得不偿失。
优缺点分析
- 继承Thread类:简单直观,但限制了继承其他类的能力。
- 实现Runnable接口:更灵活,避免了单继承限制,推荐使用。
- 使用Callable和Future:可以获取线程执行结果,但增加了编程复杂性。
继承Thread类 VS 实现Runnable接口
在如上案例演示,我们也基本对创建线程的几种方式有了一定的了解,虽然说还有很多别的方式,这里我就暂时不先普及了。学习如上,我们得知创建线程的两种主要方式是继承Thread
类和实现Runnable
接口。那么?针对这两最为经典的方式,二者有何同异?这两种方法又各有其特点和适用场景?以下是我对这两种方式的比较分析:
继承Thread类
- 简单性:直接继承
Thread
类并重写run()
方法是一种直观且简单的线程创建方式。 - 限制性:由于Java不支持多重继承,如果类已经继承了另一个类,则不能继承
Thread
类。
实现Runnable接口
- 灵活性:实现
Runnable
接口允许类继承另一个类的同时,还能具备多线程执行的能力。 - 推荐方式:由于继承的限制,Java官方推荐使用
Runnable
接口来创建线程。
优缺点分析
-
继承Thread类:
- 优点:简单直观,适合简单的线程创建。
- 缺点:由于Java的单继承特性,限制了类的继承能力。
-
实现Runnable接口:
- 优点:提供了更大的灵活性,允许类继承其他类的同时实现多线程。
- 缺点:需要额外创建
Thread
对象,代码稍显复杂。
应用场景
- 当类结构简单,且没有继承其他类的需求时,继承
Thread
类是一个快速的选择。 - 当需要实现多继承或者类已经继承了其他类时,实现
Runnable
接口是更好的选择。
结论
尽管继承Thread
类在某些简单场景下足够使用,但实现Runnable
接口提供了更大的灵活性和扩展性。因此,除非有特别的理由,通常推荐使用Runnable
接口来创建和管理线程。这样做不仅可以避免继承的限制,还可以使代码更加模块化和易于维护。
案例分析
通过上述这三种代码示例,我们可以看到Java提供了灵活的线程创建方式。继承Thread类是最基本的方法,但这种方式存在单继承的限制。实现Runnable接口是一种推荐方式,它允许开发者在不继承Thread的情况下创建线程。而使用Callable和Future则可以创建有返回值的线程任务,这在需要线程执行结果的场景中非常有用。总而言之,这需要根据具体的场景来选择不同的创建方式。
应用场景
如下我来给同学们科普下,运用多线程的一些常见场景,案例列举如下:
- Web服务器:处理多个客户端请求。
- 数据库操作:并行执行多个数据库查询。
- 图形用户界面:响应用户操作,同时进行后台处理。
类代码方法介绍
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
类、实现Runnable
接口以及使用Callable
和Future
。以下是对代码的简要说明:
-
MyThread类:继承自
Thread
类,重写了run()
方法,但代码中没有给出这个类的实现。在main
方法中,创建了MyThread
的实例并启动了线程。 -
MyRunnable类:实现了
Runnable
接口,重写了run()
方法,同样代码中没有给出这个类的实现。在main
方法中,创建了MyRunnable
的实例,并用它创建了一个新的Thread
对象,然后启动了线程。 -
MyCallable类:实现了
Callable
接口,重写了call()
方法,返回一个Integer
类型的结果。在main
方法中,创建了一个单线程的ExecutorService
,提交了MyCallable
任务,并获取了返回结果。 -
异常处理:使用
try-catch
块来捕获和处理future.get()
可能抛出的InterruptedException
和ExecutionException
。 -
线程池关闭:在所有线程任务提交并执行完毕后,调用
executor.shutdown()
来关闭线程池。
全文总结
在深入学习Java多线程的几种创建方式后,我们不难发现,多线程不仅仅是提升程序性能的工具,更是一门艺术,一种能够让我们的代码提高并高效运行效率。通过本文的学习,我们不仅回顾了线程的基本概念,如线程与进程的区别、线程的生命周期和优先级,还深入研究了Java中线程的创建和管理方法。
继承Thread类
继承Thread
类的方式简单直接,它让线程的创建变得容易,但这种简单性背后隐藏着局限——Java的单继承特性限制了我们的选择。这就像是一条只能通往一个方向的道路,虽然清晰,但缺乏灵活性。
实现Runnable接口
相比之下,实现Runnable
接口则像是打开了一扇门,让我们的类在继承其他类的同时,也能成为线程。这种方式更加灵活,也更符合Java的设计哲学。它告诉我们,即使在有限的条件下,我们也能通过创造性的方法找到解决方案。
使用Callable和Future
而Callable
和Future
的结合使用,则为我们提供了一种全新的视角——线程不仅能执行任务,还能返回结果。这就像是赋予了线程新的生命,让它们不仅能工作,还能“思考”并给出答案。
应用场景
在实际开发中,无论是处理Web服务器的并发请求,还是进行数据库的并行查询,或是在图形用户界面中响应用户操作的同时进行后台处理,多线程都扮演着至关重要的角色。
优缺点分析
每种线程创建方式都有其适用的场景和存在的价值。继承Thread
类虽然简单,但限制了类的继承能力;实现Runnable
接口虽然灵活,却需要我们手动创建Thread
对象;而Callable
和Future
虽然强大,但也带来了编程的复杂性。
全文小结
通过本文的学习,我们不仅掌握了Java多线程的创建和管理,更重要的是,我们学会了如何根据不同的应用场景,选择最合适的线程模型。我们了解到,尽管多线程能显著提升程序的性能,但在使用时也要注意线程安全和资源管理,避免并发问题的发生。
在这个多核CPU和高并发需求日益增长的时代,掌握多线程编程技术,就像是拥有了打开高效编程大门的钥匙。让我们带着这份自信和知识,继续在编程的道路上探索和前进,用多线程让我们的代码飞速运行,创造出更加出色的系统/软件/App等应用。
同学, 请记住,学习是一个永无止境的过程,而多线程编程,只是这个过程中的一部分。让我们保持好奇心,不断探索,不断学习,因为每一次深入学习,都会让我们离成为一名优秀程序员的目标更近一步。
... ...
至此,感谢阅读本文,如果你觉得有所收获,不妨点赞、关注和收藏,以支持bug菌继续创作更多高质量的技术内容。同时,欢迎加入我的技术社区,一起学习和成长。
学无止境,探索无界,期待在技术的道路上与你再次相遇。咱们下期拜拜~~
往期推荐
转载自:https://juejin.cn/post/7385966245587632180