一. 引言
现在把之前的示例还原一下,创建线程的第一种方式。(继承创建子类对象,覆盖run方法)
之前的例子如上,存在着三个线程(目前自己能够理解认识的),主线程和自己创建的两个线程。cpu在这三者之间进行切换。
现在遇到一个瓶颈,Demo类当中有一部分代码,我们需要用到多线程执行,我们就继承了Thread类(什么时候用多线程)。
为什么要有多线程的出现,就为为了并发程序,提高效率。类中需要同时运行的代码,我们称之为线程任务,对其进行封装。
怎么封装呢?线程任务代码必须要在Demo类的,run方法当中。
一定要写成run()方法么?不一定,封装的代码可以在任何名称的方法里。为了能同时执行部分代码,我们要具备Thread类中的run方法,因此我们要继承Thread类,但是最终要在run()方法调用封装在某一些方法中的并发代码,实现多线程执行。
这带来的问题就是,想要执行多线程么,继承Thread类,叫爹。但如果Demo类有父类呢?java不允许多继承,怎么来解决这个问题。
有人提出,Fu类再继承Thread类。这么弄也行,这是不得已而为之。(这样显得体系都是线程体系,线性)
如果不是线程体系,就不要这么弄,一旦继承了,那么类的所属就确定了。你就属于线程的体系了,你就具备线程里面的所有功能(这里的线程和多线程不是一个东西)。
当一个类有自己的父类的时候,它就不能继承Thread类了。→讲到这里,相当于又重新开了一个分支,不是按着线性发展的路线来了。
二.
代码要并发执行,我们需要多线程来执行。那该怎么办?不继承,我们也必须有其他方案能解决。
现在就基于下面截图中的例子,来讲解。
现在的情况就是Demo类继承了Fu类,而且Demo类中还有需要多线程执行的封装代码块show(),但是又不能多继承Thread类,该怎么办?也就是说Demo类需要一个额外的功能,能够将内部的show代码块变成线程任务。以前是通过继承,调用来的,现在没有继承类和方法了。
现在需要去扩展Demo类的功能,它已经有了爹,再扩展就是接口。是不是我们要实现Demo类的功能扩展?对嘛,那就是接口。
接口就是用来实现扩展的,一个类在继承一个类的同时,是不是实现更多的接口,实现的目的是为了什么?是为了这个类增加更多的功能。→核心的意思就是,通过实现接口,来增加类的功能,具体怎么实现是各个类自己定义的。
现在要思考人家有没有给我们提供接口?(以前讲接口的时候,貌似都是自己定义的?)
如果没提供,我们就要重新想办法(整个思考的流程是这样的,多继承不让用,我们就想通过接口来间接实现,如果连接口都没有,那我们就再想其他办法)。
现在看到底有没有接口,打开API查阅。
三.
在对Thread类继承过程中,我们重点集中在Thread类中的两个方法上,finalize方法和run方法。
在对run方法的API查询中,截图如下,
介绍中交代了,有这么一个接口,叫做runnable。→这种路径找起来是比较方便的,因为我们要做的,是给Demo类增加能封装线程任务的功能。所以,我们要找和任务相关的接口或者方法。
点一下runable,查阅
runable本身就是java.lang包中的接口,runable单词含义是可运行的,
截图中的意思就是,如果你有一个类,想让线程去执行类中的一部分内容,这个类就要实现runable接口。要扩展其功能,让其具备封装任务的能力。
接下来具体演示怎么,不通过继承,采用接口的形式实现多线程。
runable接口非常简单,其中的方法只有一个,void run(),这个run就是用来封装线程任务的。
在Demo类中覆盖run方法,这个run方法来自于,实现runable接口而来。run方法里面的代码就是线程任务。
现在就已经完成线程任务的封装了。(实现runable接口,覆盖run方法)
接下来,研究主函数中的内容。询问创建的对象d1是不是线程对象,(说这句话是为了和原继承体系中的知识点进行比较),
答案:d1不是线程对象。Demo类有自己的父类,有自己的体系,Demo类没有跟Thread类叫爹,它怎么能跟Thread类呢?它根本就不是线程对象(在这里频繁地谈论线程对象,是不是为了讲述实现接口中的线程图解做铺垫呢?以区分于继承体系中的线程?)
同样的,d1不是线程对象,自然d1.start()也是无效的。是线程才能开启,d1都不是线程对象。这就意味着原有继承体系下的程序已经无法使用了。
在这里Thread类没有子类来创建对象,形成线程。那就自己出现来形成线程,t1就是线程对象。
按着这个思路,创建了线程对象,就开始启动线程,运行线程。
DOS结果显示,没有任何结果。这里线程运行的都是Thread类自己的run方法中的内容,不是我们想要的show方法中的内容,
它在执行完自己的线程内容后,就结束了。它的run方法做了什么事儿,我不知道,但是可以保证的是,绝对做的不是我们要的show内容。
我们创建线程,是为了运行我们自己的方法。
现在的问题就是,创建的t1线程对象和我们Demo类中的run线程任务有什么关系呢?我们现在要让线程对象开启后,调用Demo类中的run方法,这个方法调用一定要具备这个方法所属的对象,(一个方法被执行,通常是被对象所调用,也可以是类名调用,目前只有这两种),这个run方法所属的对象是谁,是Demo。我们将Demo类的对象和Thread类的对象t1联合起来,就有了运行的可能。
现在就看它俩怎么形成关系。
四.
Demo类对象可以调用run方法,它俩怎么构成联系呢?Demo类实现了runable接口。线程类是你要么继承我,覆盖我的run方法,如果你要是有了自己的父类,没办法继承我,你还可以去实现一个接口,是不是这个意思?那就意味着,线程在做的时候,它对外提供了规则。那么你认为,我们是实现规则的,它是不是应该是使用规则的?是吧,笔记本为了扩展功能,对外暴露了USB接口,笔记本是使用接口了,我们做了很多USB的设备是不是在实现接口。
因此,我们来看一下,我们在查阅Thread类的时候,线程对象它的特点在于,一建立,是不是要有执行的任务?是的,线程没任务,创建它干嘛?在调用start()之前,线程就要有任务。
以上面的截图来看,我们在t1.start()之前就要有任务,应该在Thread线程对象构造的时候,赋予任务。
线程对象一创建就应该有任务,然后我们开始调用start()开启。没任务,就晚了。
在Thread类的API介绍中,除了有空参数的构造函数,还有runable接口的构造函数。这就意味着,线程对象一建立,就有了runable接口对象进来。
而runable接口对象里面,就是封装的线程任务,
因此,直接将d传递到Thread类的构造函数中,(Demo类实现了接口,它的对象怎么就是接口对象了?)
视频中说d是runable的子类对象,
截图中说“其run方法被调用的对象”,在下面的截图中,run方法被调用,就是Demo类。
最后整个程序经过编译,运行是没有问题的。
上面所说的就是线程创建的,第二种方式。这里面包含了很多步骤。
针对第三点,也就是说你不传递,线程走的是自己的run方法,你传递,就走你传递的那个。
→依照视频中的讲解来看,实现runable接口的Demo类对象,具备了可以执行的线程任务,但是自己是开启不了的。
因此,通过创建线程对象,在创建的过程中,将Demo类对象传递给线程对象,其实传递的是封装的run方法。