难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.把大象放进冰箱需要几步?
今天我们讲解如何创建一个线程,在讲解之前,我想请问大家一个问题:把大象放进冰箱需要几步?
这里可能有小伙伴就要问了,这和今天的内容有关系吗?
只能说内容上没有关系,但是思想上是有关系的,都是帮助我们理解问题,简化问题。
这个问题的答案就是:
把大象放进冰箱只需三步:打开冰箱,然后将大家放进冰箱,最后关上冰箱。
问大家这个问题也是希望大家可以以放松的心态来学习今天的内容,因为很多小伙伴也听说多线程很难掌握,所以我想通过这个故事告诉大家多线程并不难,希望大家可以放安心,跟随我的步伐,一定会掌握它的。
好了,把大象放进冰箱这个问题说完了,待会还会结合大象这个问题帮助我们理解多线程。
下面我接着来问大家一个问题。
2.我们为什么需要多线程?
因为我们需要同时执行多个任务,所以需要多线程。
下面最后问一个和“把大象放进冰箱需要几步?”类似的问题,只不过角色换成了任务和多线程。
3.让线程执行你的任务需要几步?
这个问题有小伙伴知道吗?
让线程执行你的任务只需三步:创建线程,然后将任务放进线程,启动线程。
4.Thread类
在创建线程之前,我们需要提前熟悉一个类:Thread。
通过前面的学习,我们知道,在Java中,异常(Throwable类及其子类)可以用类来描述,同样的,线程也可以用类来描述,那就是Thread类。
本章不讲解Thread类的具体内容,等创建线程章节讲解完毕后再讲解,请谅解。
异常体系
在学习异常的时候,我们知道,我们要成为一名异常,那么就得加入异常体系,加入异常体系的具体操作就是继承异常体系中的异常类。
线程体系
同理,我们要成为一名线程,那么就得加入线程体系,加入线程体系的具体操作就是继承线程体系中的线程类。
我们为什么要成为一名线程?
因为我要实现自己指定的任务,所以要成为一名线程。
综上所述,继承线程可以执行自定义任务。
5.创建线程
前面我们通过问题一步一步引入今天的主题:创建线程。
请大家再看看我们的文章标题:创建多线程之继承Thread类。
有什么发现吗?
有。为什么是“之继承Thread类”?难道还有其他创建方式?
对的,我们创建线程的方式不止一种方式,今天讲解的是继承Thread类这种方式,后面章节再讲解其他方式。
那我们就开始吧。
自定义一个类,继承自Thread类:
第一次自定义线程类,所以呢,就以打招呼的方式起了一个HiThread,意思是你好,线程。
6.将任务放进线程
线程类我们写好了。也就是上面“创建线程、将任务放进线程和启动线程”三步中的第一步我们走好了。
接下来,我们要走第二步:将任务放进线程。
有小伙伴就问了,怎么放?
上一章中,我们有一个扫地和烧水的例子,大家还记得吗?这里,我们就只简单看一下这个扫地的代码:
所谓任务,就是你要执行的代码。
“将任务放进线程”就是“将你要执行的代码放进线程”。
那么写在线程类的哪个地方合适呢?
在自定义线程类中,我们有专门用来存放要执行的代码地方,就是重写父类Thread里面的run()方法。
把我们要执行的代码写在run()方法里面。例如,扫地:
当然了,你也可以把扫地的代码写在一个方法里面,然后在run()方法里面调用:
这样的话,我们“创建线程、将任务放进线程和启动线程”三步中的前两步就做好了,现在只剩下最后一步“启动线程”没做。
等等,我们既然把扫地的任务写了,那就把烧水的任务也写一个线程类,待会我们启动线程的时候可以直接看到实际效果。
为了编程规范,我们将写有扫地任务的线程类的类名改为了SweepTheFloorThread:
烧水线程类BoilWaterThread:
7.启动线程
“创建线程、将任务放进线程和启动线程”三步已经走了两步,现在来到启动线程阶段。
启动一个线程需要该线程的实例,也就是线程对象,然后线程对象调用其start()方法就将线程启动了。
接下来,我们就来演示启动线程。
演示:
请启动线程。
请观察程序代码及结果。
代码:
BoilWaterThread类:
SweepTheFloorThread类:
Main类:
结果:
从运行结果来看,符合我们的预期。
接下来,我们来看看程序代码是怎么写的。
扫地线程类(SweepTheFloorThread)和烧水线程类(BoilWaterThread)上一小节说过了,这里就不再赘述。
下面直接看启动线程是怎么写,所以我们要看Main类中的main()方法:
首先,我们创建了扫地线程类(SweepTheFloorThread)和烧水线程类(BoilWaterThread)的对象,即创建线程对象:
接着,我们分别调用了扫地线程类(SweepTheFloorThread)和烧水线程类(BoilWaterThread)的start()方法,即启动线程,调用线程的start()方法:
然后线程就启动了,任务就开始执行了,执行结果就是:
通过执行结果,我们可以看到扫地和烧水是同时进行的。
8.五个步骤
通过这个例子,我们经历了从创建线程到将任务放进线程,再到启动线程的全部过程。从这个过程来看,我们大致可以总结出以下五个步骤:
- 继承Thread类。
- 重写run()方法。
- 将要执行的代码写在run()方法内。
- 创建线程对象。
- 调用其start()方法启动线程。
继承Thread类
重写run()方法
将要执行的代码写在run()方法内
直接写在run()方法里面
也可以写在单独一个方法里面,再使用run()方法调用
创建线程对象
调用其start()方法启动线程
9.通过匿名内部类来实现上述5个步骤
匿名内部类不清楚的小伙伴,可以点击以下文章来阅读:
全栈2019”Java第九十五章:方法中可以定义静态局部内部类吗?
全栈2019”Java第九十七章:在方法中访问局部内部类成员详解
全栈2019”Java第九十八章:局部内部类访问作用域成员详解
“全栈2019”Java第一百章:局部内部类可以实现接口吗?
“全栈2019”Java第一百零一章:局部内部类覆盖作用域内成员详解
“全栈2019”Java第一百零二章:哪些作用域可以声明局部内部类?
“全栈2019”Java第一百零四章:匿名内部类与外部成员互访详解
“全栈2019”Java第一百零五章:匿名内部类覆盖作用域成员详解
“全栈2019”Java第一百零六章:匿名内部类与抽象类接口注意事项
“全栈2019”Java第一百零七章:匿名内部类与构造方法注意事项
“全栈2019”Java第一百零八章:匿名内部类与final关键字
“全栈2019”Java第一百零九章:匿名内部类实现唯一抽象类或接口
“全栈2019”Java第一百一十章:局部内部类与匿名内部类区别详解
接下来,我们来看看匿名内部类是如何完成线程的创建到启动的。
演示:
请通过匿名内部类来创建线程并启动线程。
请观察程序代码及结果。
代码:
Main类:
结果:
从运行结果来看,符合我们的预期。
简单的来看一下我们的程序代码。
首先,我们书写了烧水方法和扫地方法:
然后我们使用匿名内部类创建并启动了烧水线程和扫地线程:
当然了,你也可以分两步进行,第一步使用匿名内部类创建线程对象:
第二步启动线程:
执行结果:
使用匿名内部类创建线程的方式在实际开发中很常见也很常见。希望大家能掌握这一技能。
10.线程可以重复启动吗?
线程不可以重复启动。
我们来试试重复启动一个线程试试。
演示:
请重复启动一个线程。
请观察程序代码及结果。
代码:
BoilWaterThread类:
SweepTheFloorThread类:
Main类:
结果:
错误信息:
文字版:
Exception in thread "main" java.lang.IllegalThreadStateExceptionat java.base/java.lang.Thread.start(Thread.java:794)at main.Main.main(Main.java:21)烧水: 10%烧水: 20%烧水: 30%烧水: 40%烧水: 50%烧水: 60%烧水: 70%烧水: 80%烧水: 90%烧水: 100%
程序发生java.lang.IllegalThreadStateException异常,IllegalThreadStateException是非法线程状态异常的意思。
什么叫非法线程状态异常?
首先,我们先搞清楚什么是线程状态?
新建:当线程被创建时。
就绪(可运行):当线程准备被执行。
阻塞:线程能够运行,但因为某些条件阻止了它的运行。
死亡:run()方法返回或线程被中断。
现在大家只需了解这四种线程状态,后面章节我们会详细讲解的。
回到我们现在的问题上:什么叫非法线程状态异常?
现在可以回答了,就是线程已经是运行状态了,你还调用一次start()方法,即启动线程,那么就会产生IllegalThreadStateException非法线程状态异常。
11.子类重写父类run()方法需要去调用父类的run()方法吗?
这个还得看情况,不能一概而论。但是,大多数情况是不需要去调用父类的run()方法。
我们来个示例演示一下。
演示:
请定义一个父类线程FuThread,重写run()方法。
请定义一个子类线程ZiThread,ZiThread继承自FuThread,重写run()方法。
请观察程序代码及结果。
代码:
FuThread类:
ZiThread类:
Main类:
结果:
从运行结果来看,我们创建并启动子类线程,在执行过程中,由于我们子类线程类里面的run()方法调用其父类的run()方法,所以也一并执行了。
这里需要注意的是,子类run()方法里面调用父类run()方法被执行时的顺序是顺序执行。
什么意思?
就是先会执行父类的run()方法,然后再接着往下执行子类的run()方法剩下的内容。因为子类run()方法里面的代码书写顺序决定了程序执行顺序:
一般而言,我们不会这样去调用父类的run()方法。这点希望大家记住。
总结
- 创建线程、将任务放进线程和启动线程。
- 继承Thread类、重写run()方法、将要执行的代码写在run()方法中、创建线程对象和启动线程。
- 使用匿名内部类创建线程。
- 线程不能重复启动。
- 一般而言,我们不会这样去调用父类的run()方法。
至此,Java中创建多线程之继承Thread类相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
下一章
“全栈2019”Java多线程第三章:创建多线程之实现Runnable接口
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!