环境说明: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线程。下面是我对该代码的解释和一些改进建议:
-
MyThread类:这个类继承自Java的
Thread类,重写了run()方法。这是线程执行的入口点,当调用start()方法时,run()方法中的代码将被执行。 -
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接口来创建一个线程。以下是对代码的解释:
-
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对象,程序能够获取任务的返回结果,并在任务完成后关闭线程池。
最后,这里附上部分相关注解源码截图,这里我就简单给附上,感兴趣的同学可以扒扒源码,深入去学习下开源框架的设计构思及理念,这也是掌握一个架构的核心目标,但是基础一般或者零基础的同学,建议先从使用上深入,而不是一口吃掉一个胖子,得不偿失。
ii.继承Thread类 VS 实现Runnable接口
在如上案例演示,我们也基本对创建线程的几种方式有了一定的了解,虽然说还有很多别的方式,这里我就暂时不先普及了。学习如上,我们得知创建线程的两种主要方式是继承Thread类和实现Runnable接口。那么?针对这两最为经典的方式,二者有何同异?这两种方法又各有其特点和适用场景?以下是我对这两种方式的比较分析:
a.继承Thread类
基本概念
Thread类是Java中用于创建和管理线程的类。通过继承Thread类并重写其run()方法,可以定义线程的执行逻辑。每个Thread对象代表一个线程,调用start()方法后,线程开始执行run()方法中的代码。
1.优点
- 简单直观:继承
Thread类的方式非常直观,尤其适合快速开发简单的多线程程序。 - 直接使用
Thread类的方法:继承Thread类后,可以直接使用Thread类提供的各种方法,如getName()、setPriority()等,方便线程的管理和控制。
2.缺点
- 不支持多重继承:Java不支持多重继承,如果一个类已经继承了其他类,就不能再继承
Thread类,这限制了这种方法的灵活性。 - 耦合性高:继承
Thread类将线程的行为与线程的实现紧密耦合,降低了代码的可重用性和扩展性。
b.实现Runnable接口
基本概念
Runnable接口是一个函数式接口,只有一个run()方法。通过实现Runnable接口,并将其作为线程的任务,可以将线程的行为与实现分离。这种方法更灵活,特别是在需要创建多个线程来执行相同任务的情况下。
1.优点
- 解耦:实现
Runnable接口将线程的行为与其实现解耦,增强了代码的灵活性和可重用性。 - 支持多重继承:由于Java支持实现多个接口,使用
Runnable接口不会限制类的继承关系,使得代码更易于扩展。 - 适合线程池:
Runnable接口非常适合与线程池一起使用,因为线程池通常会管理多个线程执行相同或不同的任务。
2.缺点
- 间接访问线程方法:使用
Runnable接口时,无法直接调用Thread类的方法,需要通过Thread.currentThread()来访问线程的相关信息,这可能会增加代码的复杂性。 - 相对复杂:与直接继承
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接口以及使用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多线程的创建和管理,更重要的是,我们学会了如何根据不同的应用场景,选择最合适的线程模型。我们了解到,尽管多线程能显著提升程序的性能,但在使用时也要注意线程安全和资源管理,避免并发问题的发生。
在这个多核CPU和高并发需求日益增长的时代,掌握多线程编程技术,就像是拥有了打开高效编程大门的钥匙。让我们带着这份自信和知识,继续在编程的道路上探索和前进,用多线程让我们的代码飞速运行,创造出更加出色的系统/软件/App等应用。
七、全文总结
在深入学习Java多线程的几种创建方式后,我们不难发现,多线程不仅仅是提升程序性能的工具,更是一门艺术,一种能够让我们的代码提高并高效运行效率。通过本文的学习,我们不仅回顾了线程的基本概念,如线程与进程的区别、线程的生命周期和优先级,还深入研究了Java中线程的创建和管理方法。
同学, 请记住,学习是一个永无止境的过程,而多线程编程,只是这个过程中的一部分。让我们保持好奇心,不断探索,不断学习,因为每一次深入学习,都会让我们离成为一名优秀程序员的目标更近一步。
至此,感谢阅读本文,如果你觉得有所收获,不妨点赞、关注和收藏,以支持bug菌继续创作更多高质量的技术内容。同时,欢迎加入我的技术社区,一起学习和成长。
学无止境,探索无界,期待在技术的道路上与你再次相遇。咱们下期拜拜~~
八、往期推荐
- 深入Java多线程:基础篇,你必须掌握!| 多线程篇(一)
- 如何创建线程?继承Thread类 VS 实现Runnable接口 | 多线程篇(二)
- 线程安全的艺术:Java中的同步方法与实践! | 多线程篇(三)
- 并发编程秘籍:Java线程间通信的深度剖析! | 多线程篇(四)
- Java并发核心:线程池使用技巧与最佳实践! | 多线程篇(五)
- Java并发工具类:构建高效多线程应用的关键!| 多线程篇(六)
- Java并发基础:原子变量在多线程同步中的专业应用!| 多线程篇(七)
- 从同步到并发:Java并发集合在现代应用中的卓越性能!| 多线程篇(八)
- Java并发设计模式:生产者-消费者模式、读写锁模式与线程池模式!| 多线程篇(九)
- Java并发编程的高级话题:Fork/Join框架和CompletableFuture的使用技巧!| 多线程篇(十)
本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!