昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的问题:你说一说 Java 创建线程都有哪些方式?
这哥们心中窃喜,这个老生常谈的问题早已背的滚瓜烂熟,于是很流利的说了出来。
Java 创建线程有两种方式:
- 继承
Thread
类,并重写run()
方法- 实现
Runnable
接口,覆盖接口中的run()
方法,并把Runnable
接口的实现扔给Thread
面试官:(拿出一张白纸)那你把这两种方式写一下吧!
小哥:(这有何难!)好~
public static void main(String[] args) {
// 第一种
MyThread myThread = new MyThread();
myThread.start();
// 第二种
new Thread(() -> System.out.println("自己实现的run-2")).start();
}
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("自己实现的run-1");
}
}
面试官:嗯,那除了这两种,还有其他创建线程的方法吗?
这些简单的问题难不倒这哥们,于是他想到了 Java5 之后的Executors
,Executors
工具类可以用来创建线程池。
小哥:Executors
工具类是用来创建线程池的,这个线程池可以指定线程个数,也可以不指定,也可以指定定时器的线程池,它有如下常用的方法:
newFixedThreadPool(int nThreads)
:创建固定数量的线程池newCachedThreadPool()
:创建缓存线程池newSingleThreadExecutor()
:创建单个线程newScheduledThreadPool(int corePoolSize)
:创建定时器线程池
面试官:嗯,OK,咱们还是针对你刚刚写的代码,我再问你个问题。
此时这哥们有种不祥的预感,是不是自己代码写的有点问题?或者要问我底层实现?
面试官:你写的两种创建线程的方式,都涉及到了run()
方法,你了解过Thread
里的run()
方法具体是怎么实现的吗?
果然问到了源码了,这哥们之前看了点,所以不是很慌,回忆了一下,向面试官道来。
小哥:emm……Thread
中的run()
方法里东西很少,就一个 if 判断:
@Override
public void run() {
if (target != null) {
target.run();
}
}
有个target
对象,会去判断该变量是否为空,非空的时候,去执行target
对象中的run()
方法,否则啥也不干。而这个target
对象,就是我们说的Runnable
:
/* What will be run. */
private Runnable target;
面试官:嗯,那这个Runnable
类你了解过吗?
小哥:了解过,这个Runnable
类很简单,就一个抽象方法:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
这个抽象方法也是run()
!如果我们使用Runnable
接口,就需要实现这个run()
方法。由于这个Runnable
类上面标了@FunctionalInterface
注解,所以可以使用函数式编程。
那小哥接着说:(突然自信起来了)所以这就对应了刚才说的两种创建线程的方式,假如我用第一种方式:继承了Thread
类,然后重写了run()
方法,那么它就不会去执行上面这个默认的run()
方法了(即不会去判断target
),会执行我重写的run()
方法逻辑。
假如我是用的第二种方式:实现Runnable
接口的方式,那么它会执行默认的run()
方法,然后判断target
不为空,再去执行我在Runnable
接口中实现的run()
方法。
面试官:OK,可以,我再问你个问题。
小哥:(暗自窃喜)
面试官:那如果我既继承了Thread
类,同时我又实现了Runnable
接口,比如这样,最后会打印什么信息出来呢?
面试官边说边拿起刚刚这小哥写的代码,对它进行了简单的修改:
public static void main(String[] args) {
new Thread(() -> System.out.println("runnable run")) {
@Override
public void run() {
System.out.println("Thread run");
}
}.start();
}
这小哥,突然有点懵,好像从来没想过这个问题,一时没有什么思路,于是回答了个:会打印 “Thread run” 吧……
面试官:答案是对的,但是为什么呢?
这小哥一时没想到原因,于是面试官让他回去可以思考思考,就继续下一个问题了。
亲爱的读者朋友,你们知道为什么吗?你们可以先思考一下。
其实这个答案很简单,我们来分析一下代码便知:其实是 new 了一个对象(子对象)继承了Thread
对象(父对象),在子对象里重写了父类的run()
方法;然后父对象里面扔了个Runnable
进去,父对象中的run()
方法就是最初那个带有 if 判断的run()
方法。
好了,现在执行start()
后,肯定先在子类中找run()
方法,找到了,父类的run()
方法自然就***掉了,所以会打印出:Thread run。
如果我们现在假设子类中没有重写run()
方法,那么必然要去父类找run()
方法,父类的run()
方法中就得判断是否有Runnable
传进来,现在有一个,所以执行Runnable
中的run()
方法,那么就会打印:Runnable run 出来。
说白了,就是问了个 Java 语言本身的父子继承关系,会优先执行子类重写的方法而已,只是借这个场景,换了个提问的方式,面试者可能一时没反应过来,有点懵也是正常的,如果直接问,傻子都能回答的出来。
后记:通过这道简单的面试题,帮大家分析了一下在创建线程过程中的源码,可以看出来,面试过程中,面试官更加看重一些原理性的东西,而不是背一下方式就行了。同时也能看的出,面试大厂,需要做好充分的准备。另外,在面试的时候要冷静,可能有些问题并没有太难,回答不出来只是当时太紧张造成的。
最后如何备战Java面试?
谈到怎么准备Java面试,我想说,若你还同无头苍蝇一般不知如何下手,那么第一件事你要做的就是梳理自身的知识脉络。毕竟,想要进大厂,就需要具备更多的知识树,需要掌握更全面的知识体系,所以就需要对知识进行一个系统的梳理了。
如果觉得自己手绘Java知识脉络图比较麻烦,可以借鉴参考下方我之前大致手绘一份知识网图(Java知识体系庞大,我是用的xmind绘制的脉络图,文中无法直接上传原件,截图如下,内容过多以至于图片不够清晰,但可提供分享原件☞☞☞原件下载 )。
此外,关于面试刷题,个人觉得这份 “Java高分面试指南”很不错,包含25个专题内容,整个1000+题50w+字解析,够你刷到秃顶:
- Java中的IO与NIO
- Java反射
- Java序列化
- Java注解
- 多线程&并发
- JVM
- Mysql
- Redis
- ........
最后,还有2021最新整理的“Java中高级核心知识点解析”,查漏补缺知识点刷它最合适不过,包含整个30类知识点的集合:
- JVM
- JAVA集合
- JAVA多线程并发
- JAVA基础
- Spring原理
- 微服务
- Netty与RPC
- ....
从学习路线→☛面试刷题→☛核心知识补漏,以上全套的Java学习资源皆可免费共享给有需求的你,还请仔细关注一下免费下载原件的方式:
1:一键三连+评论此文(码字不易,一键三连是对此文的一个认可,也可让更多朋友阅读到)
2:加小姐姐VX:k15197783918