Java多线程新手指南:从零带你开始学习多线程创建!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
前言
多线程,众多人眼中老生常谈的话题,没有之一,但在日常项目开发中它也是最为熟知且常用的知识点之一,毕竟它能够让你的程序同时执行多个任务(所谓的并发),提高程序的响应速度和资源利用率。简而言之,就是让计算机的多个核心同时工作,就像多个人同时做不同的任务。
但在Java语言里,多线程的使用极为重要,毕竟JAVA语言本身就提供了强大的多线程支持,也提供了丰富的多线程支持,使得大家在入门Java及多线程可以零压力使用。所以今天我就重点来聊聊它(一起来重温下吧)。
本文将重点介绍及演示多线程的必备基础知识点,及衍生深度穿插,保证大家在零基础的情况下能够从零学明白Java多线程,最终掌握多线程的日常开发能力。
摘要
在本期中,我将重点介绍Java多线程的几种基础创建方式,其中就包括继承Thread类和实现Runnable接口两种方式,并结合其源代码、应用场景案例、优缺点剖析等篇幅来详细讲解每一种方法的使用和特点,最后通过测试案例来完整的复盘,理论 + 实践 = (你)百分百掌握。
概述
在Java中多线程的创建,它有两种常用的方式:分别为继承Thread
类和实现Runnable
接口,这两种是最基础的创建方式,比如还有通过线程池创建、定时创建等方式,这些我们后续再详细讲。这里我先给大家科普下,对于继承Thread类这种创建方式,原理就是重写run()
方法;而实现Runnable接口这种创建方式,我们只需要实现run()
方法即可,听上去是不是非常的简单。
而针对如上两种创建方式,启动线程皆可通过创建线程对象调用start()
方法,如下我就通过此两种方式带着大家零基础入门,最终保证学习的同学在使用多线程上毫无压力。
正文
接下来,我就以先理论后实践的教学方式,给大家呈现此期内容,请同学们悉知。
一、创建线程及启动理论
如何创建线程并启动它?这点我们在实操之前是必须要清楚的,先了解它的实现逻辑思路理论,之后再结合实例上手实践贯穿理论,以此加深大家对本期知识点的理解。以下是创建和启动线程的理论版步骤教程,以及它们之间的逻辑联系。
-
定义任务:
- 在多线程编程中,每个线程都需要执行一个特定的任务。任务的定义通常通过编写一个
run()
方法来实现。这个方法是线程执行的入口点,包含了线程要执行的所有操作。
- 在多线程编程中,每个线程都需要执行一个特定的任务。任务的定义通常通过编写一个
-
选择线程创建方式:
- 根据你的程序需求,你可以选择继承
Thread
类或实现Runnable
接口来定义你的任务。继承Thread
类意味着你的类直接扩展了线程的功能,而实现Runnable
接口则需要将你的任务类传递给一个Thread
对象。
- 根据你的程序需求,你可以选择继承
-
创建线程对象:
- 一旦你定义了任务,接下来就是创建线程对象。如果你选择继承
Thread
类,你将创建该类的实例;如果实现Runnable
接口,你需要创建一个Runnable
对象,并将该对象作为参数传递给Thread
类的构造函数,然后创建Thread
对象的实例。
- 一旦你定义了任务,接下来就是创建线程对象。如果你选择继承
-
启动线程:
- 创建线程对象后,你需要调用
start()
方法来启动线程。这个方法会触发线程的执行,使其进入就绪状态,并最终运行。重要的是要注意,start()
方法会隐式地调用你的run
方法,因此无需手动调用run
。
- 创建线程对象后,你需要调用
-
线程的执行:
- 一旦线程启动,它将按照定义的
run
方法中的指令执行任务。线程的执行是由Java运行时环境的线程调度器控制的,它会根据系统的线程调度策略来决定何时执行哪个线程。
- 一旦线程启动,它将按照定义的
-
线程的生命周期管理:
- 在线程执行过程中,你需要了解线程的生命周期,包括新建、就绪、运行、阻塞和死亡状态。这有助于你更好地控制线程的执行流程,例如,通过适当的同步机制来处理线程间的协作或竞争。
-
异常处理:
- 在多线程环境中,异常处理尤为重要。确保你的
run()
方法能够妥善处理可能发生的异常,避免线程因未捕获的异常而意外终止。
- 在多线程环境中,异常处理尤为重要。确保你的
-
线程的结束:
- 线程完成任务后,应当正确结束。在
run()
方法的末尾,线程会自然结束。如果需要提前结束线程,可以通过中断机制来实现。
- 线程完成任务后,应当正确结束。在
通过上述步骤,你就可以轻松创建和启动线程,实现并行执行任务。但有一点大家需要注意,多线程使用之前需要仔细设计和测试,以确保线程间的同步和资源管理得当,以此避免出现竞态条件和死锁等问题,这点大家日常在使用多线程是务必需要理清楚的。
二、创建线程方式
那么,有了如上知识点的铺垫,那接下来,我就带着大家上手实操,把理论运用到实践上。
接下来我们就来耍耍,它究竟要如何创建?会不会很难?其实真的超级简单,我们压根不需要重复造轮子啥的,我们只需要使用Java它本身提供的原生态方法就能够满足,创建线程尤其Java还提供了多种方式来支持,而其中最基本的两种方式当属继承Thread类和实现Runnable接口。接下来,准备好大家的双手,我们要开始手撕代码了,逐步深入这两种方法的实操。
多线程创建方式1:继承Thread类
我们先来介绍第一种多线程创建方式,它的原理是通过定义一个类直接继承Thread类,然后重写run(),上手难度一颗星。
示例代码如下:
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-15 22:23
*/
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码逻辑
System.out.println("线程执行中...");
}
}
代码解析:
这里,我再对如上提供的实操案例进行一个解析,照顾一下手生的同学。如上代码实现思路如下: 先定义一个名为MyThread
的线程类,继承Thread。这个类中我重写了Thread类中的run()
方法,run()
方法中包含了线程执行的代码逻辑,按实际需要定义,这里我只是做个演示。在这个例子中,run()
方法简单打印出一条消息"线程执行中...",如果线程被调用,那这句话将会被打印。
总言而之,通过创建MyThread
的对象并调用start()
方法,可启动一个新的线程,并执行其中的代码逻辑,上手之后,是不是没啥难度。趁热打铁,这里我再讲一下如上提到的另一种方式。
多线程创建方式2:实现Runnable接口
实现Runnable接口?原理就是定义一个类直接继承Thread类,然后重写run()方法即可。上手难度一颗星。
示例代码如下:
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-15 22:24
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码逻辑
System.out.println("线程执行中...");
}
}
代码解析:
依旧是照顾基础薄弱的同学,对这种创建方式也进行一下代码解析。代码解析如下:此段代码与上个原理不太相同,但共同目的只有一个,即把线程创建出来。演示案例中,我先定义了一个名为MyRunnable
的类,并且实现Runnable
接口。Runnable
接口中只有一个run()
方法需要实现。在MyRunnable
类中,重写了run()
方法,在run()
方法中,我们可以定义线程需要执行的代码逻辑,在这个例子中,我定义了输出一条信息:"线程执行中...",而实际场景中,则是写对应的业务逻辑即可。
这样,当我们将MyRunnable
实例传给一个Thread
对象,并调用start()
方法启动线程时,线程就会执行run()
方法中的代码逻辑,即会将定义的消息打印在控制台。
三、实战测试创建方法
接下来,我们就根据我们定义好的创建的线程的两种方法,来进行简单的测试,检验是否能够按照预期结果正常复现,请让我们拭目以待。
方式一:继承Thread类-创建线程
1.测试代码
示例代码如下:
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-15 22:23
*/
public class MyThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2.测试结果展示
根据如上的测试用例,我在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。
很明显,定义线程执行体中的方法被调用了,控制台正常输出了“线程执行中...”这句话。
3.测试代码解析
根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够掌握使用。下面是我对这段代码的分析,希望能帮助到你加深理解:
-
类定义:我先定义一个名为
MyThreadTest
的公共类。在Java中,公共类意味着它可以被任何其他类访问。 -
主方法:
main
方法是Java程序的入口点。它是一个静态方法,这意味着它属于类本身而不是类的实例。这个方法接受一个字符串数组args
作为参数,这通常用于命令行参数。 -
线程创建:在
main
方法中,创建了一个MyThread
类的实例,并且调用了它的start()
方法。MyThread
类没有在这段代码中给出,但我们可以推断它是一个继承了Thread
类或者实现了Runnable
接口的类,因为它有一个start()
方法。 -
线程启动:调用
start()
方法实际上会创建一个新的线程,并在这个新线程中执行MyThread
类的run()
方法。run()
方法是Thread
类的一个抽象方法,必须在子类中实现。MyThread
类的run()
方法中应该包含了线程执行的代码。 -
多线程执行:一旦
start()
被调用,MyThread
实例将在它自己的线程中并行执行,与主线程(即包含main
方法的线程)同时运行。 -
代码不完整:这段代码只是一个框架,缺少
MyThread
类的具体实现。为了使程序完整,我们需要MyThread
类的实现,其中至少包含一个重写的run()
方法。 -
线程的生命周期:当
main
方法执行完毕,程序将结束。如果MyThread
线程还没有执行完,它将继续运行,直到它的run()
方法执行完毕。 -
线程安全:这段代码没有显示任何关于线程同步或线程安全的措施。如果
MyThread
类的run()
方法中访问共享资源,那么可能需要考虑线程安全问题。 -
错误处理:代码中没有显示任何错误处理机制。在实际应用中,线程可能会抛出异常,因此可能需要捕获并适当处理这些异常。
-
资源管理:如果
MyThread
使用了需要关闭的资源(如文件、网络连接等),则需要确保这些资源被适当地关闭,无论是在run()
方法的末尾还是在finally
块中。
方式二:实现Runnable接口-创建线程
如下是我对第二种创建方式的测试,检验是否能够按照预期结果正常复现?
1.案例测试代码
示例代码如下:
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-15 22:24
*/
public class MyRunnableTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
2.测试结果展示
根据如上的测试用例,我在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。
本地实际运行结果展示如下:
从结果中来看,可以直观看到,在执行函数中成功执行了start方法,即控制台成功输出打印了线程体中执行run方法的逻辑业务;输出了如下内容:
线程执行中...
3.测试代码解析
根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够掌握使用。下面是我对这段代码的分析,希望能帮助到你加深理解:
-
类定义:定义了一个名为
MyRunnableTest
的公共类。这个类包含了main
方法,它是程序的入口点。 -
创建
Runnable
实例:在main
方法中,首先创建了一个MyRunnable
类的实例。MyRunnable
应该是一个实现了Runnable
接口的类,这意味着它必须实现Runnable
接口中的run
方法。 -
创建线程:接着,使用
MyRunnable
实例作为目标,创建了一个新的Thread
对象。Thread
构造函数接受一个Runnable
对象作为参数,这个Runnable
对象定义了线程要执行的任务。 -
启动线程:通过调用
thread.start()
方法,新的线程被启动。这会导致MyRunnable
实例的run
方法在新线程中执行。 -
线程的并行执行:一旦
start()
方法被调用,MyRunnable
的run
方法将在它自己的线程中并行执行,与主线程(即执行main
方法的线程)同时运行。 -
代码的不完整性:这段代码没有提供
MyRunnable
类的实现。为了使程序完整,我们需要MyRunnable
类的实现,特别是run
方法的实现。 -
线程生命周期:
main
方法执行完毕后,如果MyRunnable
线程还没有执行完,它将继续运行,直到run
方法执行完毕。 -
线程安全:如果
MyRunnable
类的run
方法中访问了共享资源,那么需要考虑线程安全问题,确保对这些资源的访问是同步的。 -
错误处理:这段代码没有显示任何错误处理机制。在实际应用中,
run
方法可能会抛出异常,因此可能需要在run
方法中添加异常处理逻辑。 -
资源管理:如果
MyRunnable
使用了需要关闭的资源,如文件、网络连接等,需要确保这些资源在使用完毕后被适当地关闭。 -
线程的命名:创建
Thread
对象时,可以通过构造函数传递一个字符串参数来为线程命名,这有助于在调试时识别线程。 -
线程优先级:可以通过设置线程的优先级来影响线程的调度,但这通常不是线程管理的首选方法。
总而言之,通过动手实操体验如上两种创建方式,不知道大家可有掌握?
四、优缺点分析
我们既然整体体验下来,那么针对上述两种创建线程的方式,你们对此有何直观感受还是说这两种方式都没啥区别?之间有何优劣之分?理论上肯定其实是有区分的,虽然可能你们还体会不到,因为这也需要具体分场景,相对而论。
1.继承Thread类方式
优点:
- 简单直观,代码量较少。
缺点:
- 由于Java不支持多继承,如果已经继承了其他类,则无法再使用继承Thread类的方式创建线程。
2.实现Runnable接口方式
优点:
- 避免了单继承的限制,可以同时实现其他接口。
- 实现了解耦,线程对象与线程执行的逻辑分离。
缺点:
- 代码稍微复杂一些。
希望对它们的优劣分析,同学们在面对第一次学习的过程中,可以优先选择自己最先能掌握的方式,而不是一概而论,有条件的同学,则可以都掌握深入理解。
五、类代码方法介绍
如下我梳理了下针对如上两种创建线程的方式之间常用到的方法,仅供参考:
1.Thread类
- start(): 启动线程,使其开始执行run()方法中的代码。
- run(): 线程执行的代码逻辑,需要在子类中重写。
2.Runnable接口
- run(): 线程执行的代码逻辑,需要在实现类中实现。
希望大家在日常使用时,能够有意识去爬源码,学习Java所提供的原生方法,掌握其定义原理。
六、综合案例演示
最后,我这里通过一个综合案例,带着大家把上边的知识点串联起来,进行实战演示一波,同学们看好了。
1.测试代码
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @Date 2024-04-15 22:37
*/
public class Test {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start();
MyRunnable runnable = new MyRunnable();
Thread thread2 = new Thread(runnable);
thread2.start();
}
}
从代码上看,其实就是写了一个测试两个方式的测试类。
2.测试结果
针对如上测试代码,这里我本地进行实际测试一波,结果仅供参考,有条件的同学们也可以自己本地实践一下,预期结果也就不言而喻,肯定是符合的,大家请看。
3.测试代码解析
这里,我依旧会针对如上测试代码,给出具体的代码解读,希望能够帮助到有需要的同学。测试用例我定义了一个名为Test
的公共类,其中包含main
方法,这是Java程序的入口点。在main
方法中,创建并启动了两个线程:thread1
和thread2
。
-
注释部分:
- 代码顶部的注释包含了我本地编辑器的信息、来源和日期。这通常用于说明代码的出处和编写时间,也建议大家都设置一下。
-
main方法:
main
方法是程序执行的起点。它接收一个字符串数组args
作为参数,这个数组通常用于传递命令行参数给程序(这是学习Java最基础的了,这里我也重提一下)。
-
创建并启动线程
thread1
:MyThread
是一个继承自Thread
的类,这意味着它可能是一个自定义的线程类。在Test
类的main
方法中,创建了MyThread
的一个实例thread1
,然后调用了它的start
方法来启动线程。start
方法会创建一个新的执行线程,并在这个线程中执行MyThread
类的run
方法。
-
创建并启动线程
thread2
:MyRunnable
是一个实现Runnable
接口的类。Runnable
接口是Java中定义线程执行任务的一种方式。在Test
类的main
方法中,创建了MyRunnable
的一个实例runnable
,然后创建了一个新的Thread
对象thread2
,将runnable
作为目标(target)传递给Thread
构造函数。最后,调用thread2
的start
方法来启动线程。这将导致Thread
对象在新的执行线程中执行run
方法,run
方法将调用runnable
的run
方法。
总而言之,这个案例我向大家演示了两种在Java中创建和启动线程的方式:一种是通过继承Thread
类,另一种是通过实现Runnable
接口。两种方式都可以定义线程的执行任务,但是实现Runnable
接口的方式更加灵活,因为它允许你将任务的执行逻辑与线程的创建和控制逻辑分离开来。
但是,这里我也要多提一嘴,需要注意的是,我这个案例中并没有提供MyThread
和MyRunnable
类的实现细节,所以我们无法知道这些类的run
方法具体做了什么。为了完全理解这段代码的行为,我们需要这些类的完整定义,也就是所谓的业务逻辑,希望实战的同学能够把它运用上。
七、小结
讲解到这里,本期也就即将接近尾声了,在结束之前,我对本期内容来个收尾,辅助大家一起梳理下。本文主要通过介绍Java中多线程的两种创建方法,详细讲解了继承Thread
类和实现Runnable
接口两种创建线程方式的使用和特点。同时讲解了每种方式的优缺点,及对比了两者的优缺点,最后通过相应的类代码方法介绍和实战演示该创建线程的代码案例来充分演示这几种创建方式实践,加上对所有案例代码进行解读及分析,以此帮助薄弱的同学更快速的入手掌握。
总结
在此,通过本文的学习,我们了解了Java中多线程的创建方法,包括继承Thread
类和实现Runnable
接口两种方式。在实际开发中,我们可以根据需求选择合适的方式来创建线程,从而提高并发的性能和效率。
结尾
最后,我希望本文能够帮助大家理解并深入掌握Java中多线程的创建方法,并在实际开发中能够灵活运用它,而不仅仅是为了学而学,一定要把它运用到实际场景中去,发挥线程的功能。多线程它是有一个广阔而有趣的领域,希望大家也能够进一步深入学习和探索,提高自己在多线程编程方面的能力,这对项目开发能力有着极大的提升。
... ...
ok,以上就是我这期的全部内容啦,若想学习更多,你可以持续关注我,我会把这个多线程篇系统性的更新,保证每篇都是实打实的项目实战经验所撰。只要你每天学习一个奇淫小知识,日积月累下去,你一定能成为别人眼中的大佬的!功不唐捐,久久为功!
「学无止境,探索无界」,期待在技术的道路上与你再次相遇。咱们下期拜拜~~
转载自:https://juejin.cn/post/7381437156841619482