难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.单继承
通过前面的基础的学习,我们知道Java中只支持单继承,不支持多继承。这就意味着,当我们继承了Thread类,就无法再继承其他类。
![65061f01a26b274434a1dd8a2f5fe4a9.png](https://img-blog.csdnimg.cn/img_convert/65061f01a26b274434a1dd8a2f5fe4a9.png)
这就有很大的隐患。
比如,我们有一个任务线程:
![a9b3a84d3bb89a1b04f98ac5f60817a8.png](https://img-blog.csdnimg.cn/img_convert/a9b3a84d3bb89a1b04f98ac5f60817a8.png)
它现在需要继承一个公共任务类(抽取所有任务共性的父类):
![4211d612d9df8887ea26c091274ba619.png](https://img-blog.csdnimg.cn/img_convert/4211d612d9df8887ea26c091274ba619.png)
就无法再继承Thread类。
这就造成了一个很严重的问题,我这个任务它无法使用多线程执行任务。
如何解决这个问题呢?
类我们知道无法多继承,但是我们可以继承一个类,实现多个接口。如果有这么一个跟线程相关的接口就好了,既能让我继承类也能让我实现线程接口使用多线程执行任务。
有这么一个接口吗?
有的,那就是Runnable接口。
2.Runnable接口
任何想要线程执行的类都应该实现Runnable接口。而且类中必须要定义一个没有参数的run()方法。
我们来写一个实现Runnable接口的类。
![300d06e31481d41fca065a8c5f7dea9b.png](https://img-blog.csdnimg.cn/img_convert/300d06e31481d41fca065a8c5f7dea9b.png)
实现了Runnable接口之后呢?怎么让它跑起来呢?
前面一章中提到“每一个线程都和Thread相关”,说明要想Runnable跑起来,还得靠Thread。
于是,我们来看看Thread类里面有什么办法可以让Runnable跑起来。
来看看Thread类:
![61144bb1e2b7d8aa65f3b0a77cca6286.png](https://img-blog.csdnimg.cn/img_convert/61144bb1e2b7d8aa65f3b0a77cca6286.png)
通过查看Thread类,我们发现Thread除了一个无参的构造方法以外:
![0304607f7cfaf5840f74845797b73b47.png](https://img-blog.csdnimg.cn/img_convert/0304607f7cfaf5840f74845797b73b47.png)
还有一个传入一个参数的构造方法:
![efce2e0b36fc21f854aa92fd2aa39af7.png](https://img-blog.csdnimg.cn/img_convert/efce2e0b36fc21f854aa92fd2aa39af7.png)
这个参数正是Runnable接口类型:
![a8b3d3e36f8adf7379a7877ef6e3b267.png](https://img-blog.csdnimg.cn/img_convert/a8b3d3e36f8adf7379a7877ef6e3b267.png)
哦,原来是这样,我们将实现了Runnable接口的类的实例传递进Thread对象中,然后调用Thread对象的start()方法,就可以启动线程执行我们自定义任务了。
下面,我们就来动手实现看看。
演示:
请使用实现Runnable接口方式创建线程。
请观察程序代码及结果。
代码:
HiRunnable类:
![01d754d1e4f6dd2c050bcadb77886efb.png](https://img-blog.csdnimg.cn/img_convert/01d754d1e4f6dd2c050bcadb77886efb.png)
Main类:
![587d775d6495804b2497e29ef2687441.png](https://img-blog.csdnimg.cn/img_convert/587d775d6495804b2497e29ef2687441.png)
结果:
![b620b9a3f99d7476ea51146f6c067f5f.png](https://img-blog.csdnimg.cn/img_convert/b620b9a3f99d7476ea51146f6c067f5f.png)
从运行结果来看,符合我们预期。
我们来回顾一下程序代码,首先看的是实现了Runnable接口的HiRunnable类:
![360ade9fb527f86d52f8d04bb6f1c61a.png](https://img-blog.csdnimg.cn/img_convert/360ade9fb527f86d52f8d04bb6f1c61a.png)
它里面的run()方法输出了一句话:
![7b3e62dd971337b5575321b50b2db056.png](https://img-blog.csdnimg.cn/img_convert/7b3e62dd971337b5575321b50b2db056.png)
然后我们再来看看Main类里面的main()方法:
![382cabb542cc46117dd1c6585ceda227.png](https://img-blog.csdnimg.cn/img_convert/382cabb542cc46117dd1c6585ceda227.png)
main()方法里面第一步是创建了实现了Runnable接口的HiRunnable类的实例:
![3c3c1233972559efbf4bdcee698c09f8.png](https://img-blog.csdnimg.cn/img_convert/3c3c1233972559efbf4bdcee698c09f8.png)
然后我们创建了Thread类对象,并将HiRunnable类的实例传递进Thread对象中:
![ef166542763deeeafebc3e2306ba29bb.png](https://img-blog.csdnimg.cn/img_convert/ef166542763deeeafebc3e2306ba29bb.png)
最后我们调用了Thread对象的start()方法启动线程:
![f2b2f96252c3d4f419c3825c4355942f.png](https://img-blog.csdnimg.cn/img_convert/f2b2f96252c3d4f419c3825c4355942f.png)
执行结果:
![fd90c785ca1f8fe02d1fbc35189ad065.png](https://img-blog.csdnimg.cn/img_convert/fd90c785ca1f8fe02d1fbc35189ad065.png)
好了,至此,我们基本上掌握了Runnable接口的用法。
接下来,我们用Runnable的方式实现之前章节中扫地和烧水的问题。
注:还是希望大家可以连着看我们的教学文章,因为很可能后面章节讲解的内容是连着前面章节讲解的内容,所以希望大家可以连续的持续的看。
3.使用Runnable接口改写之前章节中的问题
现在我们就来改写烧水和扫地的问题。
为什么需要使用Runnable接口的方式改写?
因为下面我们马上要讲解使用匿名内部类的方式来实现Runnable接口,所以提前准备一个小案例。
演示:
请使用Runnable接口的方式改写烧水和扫地的问题。
请观察程序代码及结果。
代码:
BoilWaterRunnable类:
![ee52c7c2b9d608734ab8e20577ca9b42.png](https://img-blog.csdnimg.cn/img_convert/ee52c7c2b9d608734ab8e20577ca9b42.png)
SweepTheFloorRunnable类:
![c219294394e98423671d840dc9aea54b.png](https://img-blog.csdnimg.cn/img_convert/c219294394e98423671d840dc9aea54b.png)
Main类:
![40818b04709168be49252312a812bac0.png](https://img-blog.csdnimg.cn/img_convert/40818b04709168be49252312a812bac0.png)
结果:
![349867e6819849eb86dd1d17a0b4d5db.png](https://img-blog.csdnimg.cn/img_convert/349867e6819849eb86dd1d17a0b4d5db.png)
从运行结果来看,符合我们的预期。
简单的回顾一下,我们的程序代码,首先是烧水任务类BoilWaterRunnable:
![aa03fbb5b2b249992e986956852fdbeb.png](https://img-blog.csdnimg.cn/img_convert/aa03fbb5b2b249992e986956852fdbeb.png)
然后是扫地任务类SweepTheFloorRunnable:
![05925dd982be4d85604075f89b4156e8.png](https://img-blog.csdnimg.cn/img_convert/05925dd982be4d85604075f89b4156e8.png)
接着,我们来看看Main类的main()方法:
![3a2361128758c20b02d5d590d7d94db9.png](https://img-blog.csdnimg.cn/img_convert/3a2361128758c20b02d5d590d7d94db9.png)
在main()方法中,我们第一步创建了实现Runnable接口的对象:
![fa74ce97cff58c276a2e112fcc066b84.png](https://img-blog.csdnimg.cn/img_convert/fa74ce97cff58c276a2e112fcc066b84.png)
第二步,我们创建了Thread对象,并且将Runnable对象传递进Thread对象中:
![b987eebd378af0f69c2da55469a3b039.png](https://img-blog.csdnimg.cn/img_convert/b987eebd378af0f69c2da55469a3b039.png)
第三步,我们调用了Thread对象的start()方法:
![73e4bc8361c1e6d1e5d38c7887120f1f.png](https://img-blog.csdnimg.cn/img_convert/73e4bc8361c1e6d1e5d38c7887120f1f.png)
执行结果:
![8d4dabb25a410dcdf0222dfa60e760ff.png](https://img-blog.csdnimg.cn/img_convert/8d4dabb25a410dcdf0222dfa60e760ff.png)
从运行结果来看,我们的两个任务是同时执行的。
上面这种方式这也是我们实际开发中也常常写的一种方式。
4.通过匿名内部类方式实现Runnable接口
匿名内部类不清楚的小伙伴,可以点击以下文章来阅读:
全栈2019”Java第九十四章:局部内部类详解
全栈2019”Java第九十五章:方法中可以定义静态局部内部类吗?
全栈2019”Java第九十六章:抽象局部内部类详解
全栈2019”Java第九十七章:在方法中访问局部内部类成员详解
全栈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第一百一十章:局部内部类与匿名内部类区别详解
为什么写上一小节这个案例,目的就是为了说明我们可以通过匿名内部类这种方式来改写和简化程序代码,具体怎么做,来看下面的演示。
演示:
请实现匿名内部类的方式改写第3小节的程序代码。
请观察程序代码及结果。
代码:
Main类:
![17db59b2745e601e0aa1257420d9841d.png](https://img-blog.csdnimg.cn/img_convert/17db59b2745e601e0aa1257420d9841d.png)
结果:
![314ff2e79fde4436c0922ea55ca7626e.png](https://img-blog.csdnimg.cn/img_convert/314ff2e79fde4436c0922ea55ca7626e.png)
从运行结果来看,符合我们的预期。
各位,是不是很明显的感觉到代码量一下子从三个类(BoilWaterRunnable类、SweepTheFloorRunnable类和Main类)缩减到了一个类(Main类)中,这个变化是很明显的。
接下来,我们就来看看Main类中有哪些部分。
首先,我们定义了一个烧水的方法:
![d0aea5edba3c7df0656b2fbb0036eeb1.png](https://img-blog.csdnimg.cn/img_convert/d0aea5edba3c7df0656b2fbb0036eeb1.png)
然后,又定义了一个扫地的方法:
![050c6633aaacc005d2928431451e6bb0.png](https://img-blog.csdnimg.cn/img_convert/050c6633aaacc005d2928431451e6bb0.png)
我们再来看看main()方法:
![d4be4b0ca786e5bac4bf2e0bfa8a29d2.png](https://img-blog.csdnimg.cn/img_convert/d4be4b0ca786e5bac4bf2e0bfa8a29d2.png)
在main()方法内部,我们通过匿名内部类的方式,实现了Runnable接口:
![bbf2fe9f1633c7b93dcdcb82bd0e791f.png](https://img-blog.csdnimg.cn/img_convert/bbf2fe9f1633c7b93dcdcb82bd0e791f.png)
一共是两个,因为有两个任务需要执行。
一个是烧水任务:
![9fe0ad2957bab61225272173aa8d1f85.png](https://img-blog.csdnimg.cn/img_convert/9fe0ad2957bab61225272173aa8d1f85.png)
还有一个是扫地任务:
![8f8b9f14e64d0f6773cd9841bbe4a2b9.png](https://img-blog.csdnimg.cn/img_convert/8f8b9f14e64d0f6773cd9841bbe4a2b9.png)
创建了Runnable对象之后呢,我们就和之前的步骤一样,创建Thread对象,并将Runnable对象传入Thread对象中:
![267973ff394918f3ae3fbd8ded5badc0.png](https://img-blog.csdnimg.cn/img_convert/267973ff394918f3ae3fbd8ded5badc0.png)
最后调用Thread对象的start()方法启动线程:
![b524f8f77c82d5eca3562b9a1414dc06.png](https://img-blog.csdnimg.cn/img_convert/b524f8f77c82d5eca3562b9a1414dc06.png)
执行结果:
![a58bc5802607b28b675e3c1ffe363b6a.png](https://img-blog.csdnimg.cn/img_convert/a58bc5802607b28b675e3c1ffe363b6a.png)
好了,至此,我们通过匿名内部类的方式实现Runnable接口讲解完毕了,大家如果有任何疑问都可以在评论区向我提出。
6.四个步骤
通过上面的学习,我们大致可以总结以下五个使用Runnable接口的方式创建多线程的步骤:
- 自定义类实现Runnable接口。
- 创建实现Runnable接口自定义类的实例。
- 创建Thread对象并将Runnable对象通过构造方法参数传入其中。
- 调用Thread对象的start()方法启动线程。
自定义类实现Runnable接口
![3fd00a13f01de39fa257f08a99d056f5.png](https://img-blog.csdnimg.cn/img_convert/3fd00a13f01de39fa257f08a99d056f5.png)
创建实现Runnable接口自定义类的实例
![f0eb16720dbabd6c69aefd6331a2399b.png](https://img-blog.csdnimg.cn/img_convert/f0eb16720dbabd6c69aefd6331a2399b.png)
创建Thread对象并将Runnable对象通过构造方法参数传入其中
![d5522efef2360537a46964be70fca2c4.png](https://img-blog.csdnimg.cn/img_convert/d5522efef2360537a46964be70fca2c4.png)
调用Thread对象的start()方法启动线程
![57d4bf0af6a23627e661cca027389178.png](https://img-blog.csdnimg.cn/img_convert/57d4bf0af6a23627e661cca027389178.png)
7.Runnable接口源码
最后,我们附上Runnable接口的源码:
![a2bc36551f26957c393a297ea41697bc.png](https://img-blog.csdnimg.cn/img_convert/a2bc36551f26957c393a297ea41697bc.png)
去掉注释版:
![f7b5808e123e6da586d8ba1827669a5a.png](https://img-blog.csdnimg.cn/img_convert/f7b5808e123e6da586d8ba1827669a5a.png)
我们发现Runnable接口里面只有一个抽象方法:run()。所以,我们在实现Runnable接口时,必须实现run()方法。
总结
- 任何想要线程执行的类都应该实现Runnable接口。而且类中必须要定义一个没有参数的run()方法。
- 我们可以通过匿名内部类方式来实现Runnable接口。
- 自定义类实现Runnable接口。
- 创建实现Runnable接口自定义类的实例。
- 创建Thread对象并将Runnable对象通过构造方法参数传入其中。
- 调用Thread对象的start()方法启动线程。
至此,Java中创建多线程之实现Runnable接口相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java多线程第二章:创建多线程之继承Thread类
下一章
“全栈2019”Java多线程第四章:设置和获取线程名称
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
![2f781f212240eb51dbfd38de7dfc022e.png](https://img-blog.csdnimg.cn/img_convert/2f781f212240eb51dbfd38de7dfc022e.png)
版权声明
原创不易,未经允许不得转载!