正文
一、线程面试题分析
昨天学习了使用匿名内部类创建线程的两种方式:
现在有一道和其相关的面试题。
如果你能够回答上来,那么线程的创建原理你算是完全弄明白了;
如果你不能回答上来,那么希望通过今天对这道面试题的分析让你完全弄明白;
代码在下图,问打印的是刘小爱,还是刘大爱?
可以花个十秒钟,先看看代码,不用着急往下看哈。
老实说,当我看到这个题目的时候,我的内心是拒绝的,有一种一拳打在棉花上,有力无处使的感觉。
具体什么意思呢?
就是每一个单词我都认识,代表着什么意思我也能说出来:Thread是一个类,Runnable是一个接口,然后还有两个重写的run方法,最后就是线程开启的start方法。
很简单吧,没有一个不认识的,但它们全部合在一起,看得我就很懵逼了。
什么原因呢?我想大概还是因为我自己写的代码太少了,见识的也少,自然有的代码也就看不明白了。
所以我决定,将其一步一步地拆分:
以上就是将第一张图拆分后的代码,
第一张图:没有给对象定义一个名字,就直接使用了(这也称之为匿名对象),代码看起来就不是很清楚;
第二张图:我将对象都定义了一个名字,然后再使用这个名字,相对而言看起来就清晰一些。
我们来分析第二张图:
①这是一个Runnable的实现类对象
使用的就是匿名内部类。
Runnable是一个接口,它是没法实例化创建对象的,所以需要它的实现类创建对象,但不想再重新定义一个类了,就可以直接使用匿名内部类。
因为我没有名字,我就用我的父接口的名字来代替。
②这是一个Thread的子类对象
使用的也是匿名内部类。
Thread thread=new Thread(run);
如果是这样写的,那么创建的是啥?
这是创建对象的基本格式,是Thread类的对象。
但后面有一个大括号了,也就是说我所创建的是
Thread的子类对象
,但不想再重新定义一个类,我就用我的父类的名字代替。
说白了这道面试题就是将创建线程的两种方式结合起来了。
想彻底弄明白这道题目,我们就得去研究下run方法到底是怎么回事。
二、Thread中的run方法
start方法一被调用,代表着啥?
代表着线程启动了,同时Java虚拟机会调用run方法,执行run方法里的内容。
这也很好理解,不然线程只是启动了,没输出,要它有何用?
我们看下Thread的run方法源码:
这就是Java中Thread类的run方法,这已经是Java内部开发人员编写好的,我们没法修改。
那么现在问题来了,target是啥呢?
还记得昨天说过的Thread类的4种构造方法么?
回顾一下:
Thread():无参构造,创建一个线程对象。
Thread(String name):给线程对象指定一个名字。
Thread(Runnable target):分配一个指定的线程对象。
Thread(Runnable target,String name):分配一个指定的线程对象并指定名字。
看到没有,构造方法里面就有target。
也就是说,我们创建了一个Runnable的实现类对象run,我们将它作为参数传进了Thread里面,它就是target。
现在明白Thread的run方法了吧:
①如果Thread对象在创建时没有初始化赋值:
那么它的run方法里是没有输出的(因为并没有传值给target),也就是光启动了一个线程,啥都没有。
所以,通过Thread的子类创建线程这种方式,
本质是在重写Thread的run方法
,让它有了输出内容。
②如果Thread对象在创建时初始化赋值了:
传进去的参数是我们创建的一个Runnable的实现类对象。那么Thread执行的run方法就是我们传进去的参数的run方法,
target.run()
就是执行Runnable的实现类的run方法。
所以:通过Runnable的实现类创建线程这种方式,
本质上是在给Thread类中的run方法填充内容
,Thread的run方法就是在调用我的run方法。
它并没有重写Thread的run方法,重写的是接口Runnable的方法。
③现在我们回过头来看一开始的面试题:
拆分后的代码图,再仔细看看:
①是啥?
①是作为参数给Thread初始化赋值了的(也就是那个target)。
现在Thread中的run方法是啥?
还记得源码中的内容么?:
target.run()
,这就是在调用①中的方法呀。
也就是说,现在Thread的run方法执行的就是①中的run方法。
②是啥?
②是Thread的子类对象,它干了一件什么事情?它重写了Thread的run方法。
好现在问题来了:
①是相当于给Thread中的run方法填充内容。
②是在重写Thread的run方法。
运行结果是啥?
父类引用指向子类对象,在调用方法时,执行的是子类重写后的方法
,这是多态里的知识点,也就是说②将①给覆盖了。
还记得那句口诀么?
编译看左边,运行看右边
②中左边是啥?Thread引用,也就是父类引用。
②中右边是啥?Thread的子类对象。
编译看左边
,左边是Thread类引用,它有start方法么?它有,所以编译不报错,能运行。
运行看右边
,右边是Thread的子类对象,所以运行时执行的就是子类对象的方法。
好现在做一个小结:
Thread的start方法启动,Java虚拟机会调用Thread的run方法,输出内容。
继承Thread重写run方法,重写的就是Thread的run方法。
实现Runnable接口重写run方法,这个run方法和Thread的run方法没有直接联系,但是如果Runnable的实现类对象作为构造参数传递给Thread了(
也就是target
),Thead的run方法就会调用Runnable的实现类重写的run方法(
target.run()
)
总结